using System;
using System.Text;
using System.Collections;
using System.Threading;
using System.Diagnostics;
using System.Globalization;
using System.ComponentModel;
using Waymex.Gps.Garmin;
using Waymex.Diagnostics;
using Waymex.Gps.Garmin.Usb;


namespace Waymex.Gps
{
    /// <summary>
    /// This object is the Garmin device object though which all data objects and collections 
    /// are accessd.
    /// </summary>
    /// <example>C#
    /// <code>
    /// private void GetWaypoints()
    /// {
    ///     GarminDevice gps = new GarminDevice();
    ///
    ///     //Retrieve the Waypoints
    ///     WaypointCollection wpts = gps.GetWaypoints();
    /// 
    ///     //Now we have a collection of Waypoints
    ///     foreach (Waypoint wpt in wpts)
    ///     {
    ///         Console.WriteLine(wpt.Latitude);
    ///         Console.WriteLine(wpt.Longitude);
    ///     }
    ///
    ///     //Alternatively display the results in a DataGridView
    ///     dataGridView1.DataSource = wpts.ToDataSet();
    ///     dataGridView1.DataMember = "Waypoints";
    ///
    /// }
    /// </code>
    /// </example>
    /// <remarks>
    /// <para>Example code for individual methods is shown with the help for that method.</para>
    /// <br/><br/>
    /// <para><b>Overwriting of Identically-named Data</b></para>
    /// <para><b>Waypoints</b></para>
    /// <para>
    /// When receiving data from the Host, most GPS devices will overwrite any existing 
    /// waypoints with the same name stored in the GPS device. For example, if the Host 
    /// sends a waypoint named CBELLS, most GPS devices will overwrite a waypoint named
    /// CBELLS that may be stored in GPS memory.
    /// </para>
    /// <para>
    /// However some GPS devices compare the position of the new waypoint with the position
    /// of the stored waypoint (altitude is ignored). If the positions match, the GPS
    /// will erase the identically named waypoint and replace it with the new one.
    /// If the positions differ, the unit will create a new, unique name for the
    /// incoming waypoint and preserve the existing waypoint under its original name.
    /// </para>
    /// <para>
    /// When the Host requests the GPS to send waypoints, the GPS will send every
    /// waypoint stored in its database. When the Host sends waypoints to the GPS,
    /// the Host may selectively transfer any number of waypoints.
    /// </para>
    /// <para><b>RouteCollection</b></para>
    /// <para>
    /// When the Host requests the GPS to send routes, the GPS will send every route stored in its
    /// database. When the Host sends routes to the GPS, the Host may selectively transfer any
    /// number of routes.
    /// </para>
    /// <para>
    /// Some GPS devices contain an internal database of waypoint information; for example, most
    /// aviation products have an internal database of aviation waypoints, and others may have an
    /// internal database of land waypoints. When routes are being transferred from the Host to one
    /// of these units, the GPS will attempt to match the incoming route waypoints with waypoints in
    /// its internal database. The GPS inspects the 'Class' member of the incoming route waypoint
    /// and if it indicates a non-user waypoint, the GPS searches its internal database using either
    /// the 'Identifier' and 'Country' members or the 'Sub Class' member. If a match is found, the
    /// waypoint from the internal database is used for the route; otherwise, a new user waypoint is
    /// created and used.
    /// </para>
    /// <para><b>Tracklogs</b></para>
    /// <para>
    /// Most GPS products store only one track log (called the 'active' track log), however, some
    /// newer GPS products can store multiple track logs (in addition to the active track log). When
    /// the Host requests the GPS to send track logs, the GPS will send every Tracklog stored in its
    /// database. However only the Active Tracklog will contain Date/Time data.	
    /// </para>
    /// <para><b>Proximity Waypoints</b></para>
    /// <para>
    /// When the Host requests the GPS to send proximity waypoints, the GPS will send all proximity
    /// waypoints stored in its database.
    /// </para>
    /// <br/><br/>
    /// <para><b>Character Sets</b></para>
    /// <para>
    /// The following table shows the supported character sets;
    /// </para>
    /// <list type="table">
    /// <item><term>User Waypoint Identifier</term><description>upper-case letters, numbers</description></item>
    /// <item><term>Waypoint Comment</term><description>upper-case letters, numbers, space, hyphen</description></item>
    /// <item><term>Route Comment</term><description>upper-case letters, numbers, space, hyphen</description></item>
    /// <item><term>City</term><description>ignored by GPS</description></item>
    /// <item><term>State</term><description>ignored by GPS</description></item>
    /// <item><term>Facility Name</term><description>ignored by GPS</description></item>
    /// <item><term>Country Code</term><description>upper-case letters, numbers, space</description></item>
    /// <item><term>Route Identifier</term><description>upper-case letters, numbers, space, hyphen</description></item>
    /// <item><term>Route Waypoint Identifier</term><description>any ASCII character</description></item>
    /// <item><term>Link Identifier</term><description>any ASCII character</description></item>
    /// <item><term>Track Identifier</term><description>upper-case letters, numbers, space, hyphen</description></item>
    /// </list>
    /// <para>
    /// Some products may allow additional characters to be used. 
    /// The Host should be prepared to receive any ASCII character from the GPS, 
    /// but only transmit the characters shown above, back to the GPS.
    /// </para>
    /// <br/><br/>
    /// </remarks>
    //[LicenseProvider(typeof(ExpiringLicenseProvider))]
    public class GarminDevice : DeviceBase, IDisposable
    {
        #region Constants

        //error object
        private const string vbModule = "Device";
        private const string ERR_MSG_3000 = "Error Transferring Waypoints.";
        private const string ERR_MSG_3001 = "Error Transferring Routes.";
        private const string ERR_MSG_3002 = "Protocol Not Supported.";
        private const string ERR_MSG_3003 = "Error Transferring Position Data.";
        private const string ERR_MSG_3004 = "Error Transferring Date and Time Information.";
        private const string ERR_MSG_3005 = "Error Transferring Almanac Data.";
        private const string ERR_MSG_3006 = "Error Transferring Proximity Waypoint Data.";
        private const string ERR_MSG_3007 = "Error Transferring Track Log Data.";
        private const string ERR_MSG_3008 = "Error Transferring Protocol Table.";
        //private const string ERR_MSG_3009 = "This method is not available in this version of the GPS Library.";
        //		private const string ERR_MSG_3010 = "The argument must be greater than zero.";
        //		private const string ERR_MSG_3011 = "The argument length must be greater than zero.";
        //		private const string ERR_MSG_3012 = "The file name must be between one and eight characters in length.";
        //		private const string ERR_MSG_3013 = "The folder name must be specified.";
        private const string ERR_MSG_3014 = "Error Transferring Flight Book Data.";
        private const string ERR_MSG_3015 = "Error Transferring Product Data.";
        private const string ERR_MSG_3016 = "PVT Data is currently running.";
        private const string ERR_MSG_3017 = "Error Transferring Lap Data.";
        private const string ERR_MSG_3018 = "Argument is less than the minimum accepted value.";
        private const string ERR_MSG_3019 = "Argument is greater than the maximum accepted value.";

        private const string ERR_PORT_RANGE = "The value for port must be between 1 and 256 must be specified.";
        private const string ERR_BAUD_RANGE = "The value for speed must be one of the following: 1200, 4800, 9600, 19200, 57600 or 115200.";
       
        private const string MSG_PROPERTY_NOT_SUPPORTED = "The Property is not supported in by this device.";
        private const string MSG_METHOD_NOT_SUPPORTED = "The Method is not supported in by this device.";
        private const string MSG_METHOD_NOT_SUPPORTED_IN_PE = "The Method is not supported by this version of the product.";

        //misc constants
        private const short NOSUPPORT = -1;
        private const double PI = 3.1415926535898;
        private const double PROTOCOL_TIMEOUT = 1.0;

        //thread stuff for PVT etc
        private Thread pvtThread = null;
        private ReaderWriterLock readWriteLock = new ReaderWriterLock();
        private object syncLock = new object();

        #endregion

        #region Member Variables

        //used store an instance of the link layer
        private Link mLink;


        private UsbLink mUSB;


        //default to usb
        int m_speed = 9600;
        int m_port = 0; //MUST default to USB
        string m_usbPortName = "";

        private bool mbUSBTransport = true;

        private ProductInfo mobjProdInfo;			//product info object
        private bool mbCharsetRules = true;			//default to assert Caracter Set Rules

        //		private double mdLatitude;			//Holds current position
        //		private double mdLongitude;			//Holds current position
        //		private Position mobjPosition;		//Holds current position

        private WaypointCollection mcWayPoints;	 			//Holds the waypoints received from the GPS
        private WaypointCollection mcProximityWaypoints; 		//Holds the proximity waypoints received from the GPS
        private RouteCollection mcRoutes;						//Holds the collection of route objects
        private AlmanacCollection mcAlmanacs; 				//Holds the almanac
        private Tracklog mTrackLog;	 				//Holds a single tracklog
        private TracklogCollection mTrackLogs; 				//Holds all the tracklogs
        private FlightBookCollection mcFlightBooks; 			//Holds the FlightBooks
        private LapCollection mcLaps; 						//Holds the FlightBooks

        //used to store the protocol capabilities table
        private System.Collections.Hashtable mcProtocolTable;

        //random error for the trial version
        private float mdblRandomError = 0;

        //refresh flags
        //		private bool mbRefreshRTE;
        //		private bool mbRefreshWPT;
        //		private bool mbRefreshTRK;
        //		private bool mbRefreshPRXWPT;
        //		private bool mbRefreshALM;
        //
        //misc flags

        /// <summary>
        /// This private class is used as a thread safe class to hold status flags. 
        /// </summary>
        private class StatusFlags
        {
            private bool m_connected = false; //GPS Connected, populated but not currently used
            private bool m_transferAbort = false;
            private bool m_pvtRunning = false; //cancel flag for the event based PVT data
            
            private object syncLock = new object();

            internal bool Connected
            {
                get
                {
                    lock (syncLock)
                    {
                        return m_connected;
                    }
                }
                set
                {
                    lock (syncLock)
                    {
                        m_connected = value;
                    }
                }
            }
            internal bool TransferAbort
            {
                get
                {
                    lock (syncLock)
                    {
                        return m_transferAbort;
                    }
                }
                set
                {
                    lock (syncLock)
                    {
                        m_transferAbort = value;
                    }
                }
            }
            internal bool PvtRunning
            {
                get
                {
                    lock (syncLock)
                    {
                        return m_pvtRunning;
                    }
                }
                set
                {
                    lock (syncLock)
                    {
                        m_pvtRunning = value;
                    }
                }
            }
        }

        StatusFlags flags;
        //private bool mbConnected; //GPS Connected, populated but not currently used
        //private bool mbTransferAbort;
        //private bool mbPVTRunning; //cancel flag for the event based PVT data
        private bool mbBusy; //used to indicate the status of the position and date and time transfers
        private bool mblnDisposed; //prevents multiple calls to dispose causing problems

        //device command ID table
        private int[] miDCdata = new int[17];

        //[jn] removed as caused problems on XP with .Net 2.0
        //private System.Threading.Timer m_tickTimer = null; //controls the Tick event
        //private int m_interval = 30; //defaults to 30 seconds
        //private bool m_enabled = false;
        ///// <summary>
        ///// Gps Data event.
        ///// </summary>
        //public event DataEventHandler DataEvent;
        //public delegate void DataEventHandler(object sender, DataEventArgs EventArguments);

        #endregion

        #region PVTData Event

        //this is the trace class (it is passed to Link in the constructor)
        //private TraceLog m_Trace;

        //events
        /// <summary>
        /// The PVTData event fires when PVT data is present. 
        /// The data is passed to the event as a semi-colon delimited string in 
        /// the following format;
        /// Altitude=141.2004;EPE=36.83858;EPH=22.13921;EPV=31.80552;Fix=3;TOW=139789.999959085;Latitude=52.4758133440362;Longitude=-1.43118810277763;East=-4.838048E-03;North=-1.488053E-02;Up=2.100724E-02;MSLHeight=-48.64653;LeapSeconds=13;WNDays=4914 
        /// </summary>
        /// <remarks>
        /// <para>
        /// The event will not fire unless the PVTStart method has been invoked. 
        /// Many GPS devices will cancel PVT data when other functions are used in 
        /// this case the event will not fire however the library will continue 
        /// to monitor the GPS device for PVTData until an explicit call to the 
        /// CancelTransfer method is made.
        /// </para>
        /// <para>
        /// The parameters of the PVT Data are as follows:
        /// </para>
        /// <list type="bullet">
        /// <item><description>Altitude</description></item>
        /// <item><description>EPE</description></item>
        /// <item><description>EPH</description></item>
        /// <item><description>EPV</description></item>
        /// <item><description>Fix</description></item>
        /// <item><description>TOW</description></item>
        /// <item><description>Latitude</description></item>
        /// <item><description>Longtitude</description></item>
        /// <item><description>East</description></item>
        /// <item><description>North</description></item>
        /// <item><description>Up</description></item>
        /// <item><description>MSLHeight</description></item>
        /// <item><description>LeapSeconds</description></item>
        /// <item><description>WNDays</description></item>
        /// </list>
        /// <para>
        /// The 'MSLHeight' parameter gives the height of the WGS 84 ellipsoid 
        /// above mean sea level at the current position.
        /// </para>
        /// <para>
        /// The 'Altitude' parameter provides the altitude above the WGS 84 ellipsoid. 
        /// To find the altitude above mean sea level, add 'MSLHeight' to 'Altitude'.
        /// </para>
        /// <para>
        /// The 'TOW' parameter provides the number of seconds (excluding leap seconds) 
        /// since the beginning of the current week, which begins on 
        /// Sunday at 24:00 (i.e., midnight Saturday night-Sunday morning). 
        /// The parameter is based on Universal Coordinated Time (UTC), except 
        /// UTC is periodically corrected for leap seconds while 'TOW' is not corrected 
        /// for leap seconds. To find UTC, subtract the value of the 'Leap Seconds' 
        /// parameter from 'TOW'. Since this may cause a negative result for the first 
        /// few seconds of the week (i.e., when 'TOW' is less than 'Leap Seconds'), care 
        /// must be taken to properly translate this negative result to a positive time 
        /// value in the previous day. Also, since 'TOW' is a floating point number and may 
        /// contain fractional seconds, care must be taken to properly round off when using 'TOW' 
        /// in integer conversions and calculations. The 'WNDays' parameter provides the 
        /// number of days that have occurred from UTC December 31st, 1989 to the beginning 
        /// of the current week (thus, 'WNDays' always represents a Sunday). To find the 
        /// total number of days that have occurred from UTC December 31st, 1989 to the 
        /// current day, add 'WNDays' to the number of days that have occurred in the 
        /// current week (as calculated from the 'TOW' parameter).
        /// </para>
        /// <para>
        /// The values for the 'Fix' parameter are shown below. It is important for 
        /// the Host to inspect this value to ensure that other parameters are valid. 
        /// No indication is given as to whether the GPS is in simulator mode versus 
        /// having an actual position fix.
        /// </para>
        /// <list type="table">
        /// <listheader><term>Value</term><description>Description</description></listheader>
        /// <item><term>0</term><description>Failed Integrity Check</description></item>
        /// <item><term>1</term><description>Invalid or Unavailable</description></item>
        /// <item><term>2</term><description>Two Dimensional</description></item>
        /// <item><term>3</term><description>Three Dimensional</description></item>
        /// <item><term>4</term><description>Two Dimensional Differential</description></item>
        /// <item><term>5</term><description>Three Dimensional Differential</description></item>
        /// </list>
        /// <para>
        /// Some GPS Devices using eary internal software versions use different values for Fix.
        /// The list of devices and software versions are as follows;
        /// </para>
        /// <list type="table">
        /// <listheader><term>GPS Device</term><description>Software Versions</description></listheader>
        /// <item><term>eMap</term><description>2.64</description></item>
        /// <item><term>GPSMap 162</term><description>2.62</description></item>
        /// <item><term>GPSMap 295</term><description>2.19</description></item>
        /// <item><term>eTrex</term><description>2.10</description></item>
        /// <item><term>eTrex Summit</term><description>2.07</description></item>
        /// <item><term>StreetPilot III</term><description>2.10</description></item>
        /// <item><term>eTrex Japanese</term><description>2.10</description></item>
        /// <item><term>eTrex Venture/Mariner</term><description>2.20</description></item>
        /// <item><term>eTrex Europe</term><description>2.03</description></item>
        /// <item><term>GPS 152</term><description>2.01</description></item>
        /// <item><term>eTrex Chinese</term><description>2.01</description></item>
        /// <item><term>eTrex Vista</term><description>2.12</description></item>
        /// <item><term>eTrex Summit Japanese</term><description>2.01</description></item>
        /// <item><term>eTrex Summit</term><description>2.24</description></item>
        /// <item><term>eTrex GolfLogix</term><description>2.49</description></item>
        /// </list>
        /// <list type="table">
        /// <listheader><term>Value</term><description>Description</description></listheader>
        /// <item><term>1</term><description>Failed Integrity Check</description></item>
        /// <item><term>2</term><description>Invalid or Unavailable</description></item>
        /// <item><term>3</term><description>Two Dimensional</description></item>
        /// <item><term>4</term><description>Three Dimensional</description></item>
        /// <item><term>5</term><description>Two Dimensional Differential</description></item>
        /// <item><term>6</term><description>Three Dimensional Differential</description></item>
        /// </list>
        /// </remarks>
        /// <example>C#
        /// <code><![CDATA[
        /// 
        /// // Register the event, typically in Form_Load
        /// 
        /// gps.PvtData += new EventHandler<PvtDataEventArgs>(gps_PVTData);
         /// // If neccessary cast the gps object as follows
        /// //((GarminDevice)gps).PvtData += new EventHandler<PvtDataEventArgs>(gps_PVTData);
        /// 
        /// // Use a delegate as Windows Forms Controls are not thread safe
        /// private delegate void DisplayPvtDataDelegate(string pvtData);
        ///
        /// // The Event is then raised by the assembly when data is received from the NMEA device.
        /// private void gps_PVTData(object sender, Waymex.Gps.Garmin.PvtDataEventArgs e)
        /// {
        ///	// Call the Display function
        ///	DisplayPvtData(e.PvtData);
        /// }
        /// 
        /// private void DisplayPvtData(string pvtData)
        /// {
        ///
        ///    // Populate a label called lblSentence
        ///
        ///    // Do NOT do this, as we may not be on the UI thread.
        ///    // lblPVTDateTime.Text = DateTime.Now.ToString();
        ///
        ///    // Check if we need to call Invoke.
        ///    if (this.InvokeRequired)
        ///    {
        ///		// Pass the this function to Invoke,
        ///		// then the call will come in on the correct
        ///		// thread and InvokeRequired will be false.
        ///		this.BeginInvoke(new DisplayPvtDataDelegate(DisplayPvtData), new object[] { pvtData });
        /// 		return;
        ///	}
        ///
        ///	// now we are on the UI thread so we can populate the labels etc
        ///	lblPVTDateTime.Text = DateTime.Now.ToString("dd-mmm-yyyy hh:mm");
        ///
        ///	if (pvtData.StartsWith("Type=Satellite"))
        ///		lblSatellite.Text = pvtData;
        ///
        ///	if (pvtData.StartsWith("Type=PVTData"))
        ///		lblPVTData.Text = pvtData;
        ///
        ///	if (pvtData.StartsWith("Type=ReceiverMeasurement"))
        ///		lblRecMeas.Text = pvtData;
        ///	}
        /// ]]></code>
        /// </example>

        public event EventHandler<PvtDataEventArgs> PvtData;

        //not required in .Net 2.0
        //public delegate void PvtDataEventHandler(object sender, PvtDataEventArgs e);

        //use the following code to raise the event
        //
        //		if (PVTData != null)
        //		{
        //			PVTData(this, new PVTEventArgs(<string>));
        //		}

        #endregion

        #region Progress Event

        /// <summary>
        /// The progress event fires during the data transfers to and from the GPS. 
        /// A long value of between 0 and 100 is passed to the event. 
        /// It is provided to allow a client application to display a progress 
        /// indicator during transfers.
        /// </summary>
        /// <example>C#
        /// <code><![CDATA[
        ///	// Register the event, typically in Form_Load
        ///	gps.Progress +=new EventHandler<ProgressEventArgs>(gps_Progress);
        ///
        ///	private void gps_Progress(object sender, ProgressEventArgs e)
        ///	{
        ///		// Update a progress bar. This is OK as this event
        ///		// is raised on the thread that created the Device object.
        ///		pbProgress.Value = e.Progress;
        ///	}
        /// ]]></code>
        /// </example>
        public override event EventHandler<ProgressEventArgs> Progress;

        #endregion

        #region Private Enumerators

        private enum GPSCommit
        {
            CommitWaypoints = 1,
            CommitRoutes = 2
        }

        private enum CharSet
        {
            UserWaypointIdentifier = 1,
            WaypointComment = 2,
            RouteComment = 3,
            City = 4,
            State = 5,
            FacilityName = 6,
            CountryCode = 7,
            RouteIdentifier = 8,
            RouteWaypointIdentifier = 9,
            LinkIdentifier = 10,
            TrackIdentifier = 11,
            ShapeFile = 12
        }
        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Device Constructor.
        /// </summary>
        public GarminDevice()
            : base()
        {
            mLink = new Link();
            mUSB = new UsbLink();
            flags = new StatusFlags();
            mobjProdInfo = new ProductInfo();
            SetDCValues(DCProtocol.A010);

            //[jn] Removed as it causes problems with .Net 2 on XP
            //m_tickTimer = new System.Threading.Timer(new TimerCallback(tickTimer_TimerCallback), null, System.Threading.Timeout.Infinite, m_interval / 1000);
        }
        // Dispose executes in two distinct scenarios.
        // If disposing is true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed.
        // If disposing equals false, the method has been called by the runtime
        // from inside the finalizer and you should not reference other    
        // objects. Only unmanaged resources can be disposed.
        /// <summary>
        /// Closes all resources and disposes of the object. 
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Closes all resources and disposes of the object. 
        /// </summary>
        protected virtual void Dispose(bool disposing)
        {
            //Check to see if Dispose has already been called.
            if (!mblnDisposed)
            {
                //If disposing equals true, dispose all managed 
                //and unmanaged resources.
                if (disposing)
                {
                    //Dispose managed resources.
                    mLink.Dispose();
                    mLink = null;
                    mUSB.Dispose();
                    mUSB = null;
                }
                //Release unmanaged resources. If disposing is false,
                //only the following code is executed.      
                //Note that this is not thread safe.
                //Another thread could start disposing the object
                //after the managed resources are disposed,
                //but before the disposed flag is set to true.

            }
            mblnDisposed = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// This property determines whether the Garmin character set rules are imposed.
        /// The property is set to true by default which prevents spaces and other characters
        /// being used in Identifiers. Care should be taken, when setting this property
        /// to false, to ensure only valid characters are sent to the GPS device.
        /// </summary>
        /// <remarks>
        /// <para><b>Character Sets</b></para>
        /// <para>
        /// The following table shows the supported character sets;
        /// </para>
        /// <list type="table">
        /// <item><term>User Waypoint Identifier</term><description>upper-case letters, numbers</description></item>
        /// <item><term>Waypoint Comment</term><description>upper-case letters, numbers, space, hyphen</description></item>
        /// <item><term>Route Comment</term><description>upper-case letters, numbers, space, hyphen</description></item>
        /// <item><term>City</term><description>ignored by GPS</description></item>
        /// <item><term>State</term><description>ignored by GPS</description></item>
        /// <item><term>Facility Name</term><description>ignored by GPS</description></item>
        /// <item><term>Country Code</term><description>upper-case letters, numbers, space</description></item>
        /// <item><term>Route Identifier</term><description>upper-case letters, numbers, space, hyphen</description></item>
        /// <item><term>Route Waypoint Identifier</term><description>any ASCII character</description></item>
        /// <item><term>Link Identifier</term><description>any ASCII character</description></item>
        /// <item><term>Track Identifier</term><description>upper-case letters, numbers, space, hyphen</description></item>
        /// </list>
        /// <para>
        /// Some products may allow additional characters to be used. 
        /// </para>
        /// <br/><br/>
        /// </remarks>

        public bool CharacterSetRules
        {
            get
            {
                return mbCharsetRules;
            }
            set
            {
                mbCharsetRules = value;
            }
        }
        /// <summary>
        /// Gets/Sets the communications port to use.
        /// </summary>
        [Obsolete("Property depreciated. Use the OpenPort() and ClosePort() methods to set a communication port.")]
        public override Port CommunicationsPort
        {
            get
            {
                return (Port)m_port;
            }
            set
            {
                m_port = (int)value;
            }
        }
        /// <summary>
        /// Sets/Gets the Baud rate of the serial connection.
        /// Not applicable for USB connected devices.
        /// </summary>
        [Obsolete("Property depreciated. Use the OpenPort() and ClosePort() methods to set a communication port.")]
        public override BaudRate BaudRate
        {
            get
            {
                return (BaudRate)m_speed;
            }
            set
            {
                m_speed = (int)value;
            }
        }


        #endregion

        #region Public Methods
        /// <summary>
        /// This method returns an array of USB port names for any connected USB GPS devices. The OpenPort() method
        /// can accept a USB port name as a parameter in order to specifiy a particular USB GPS device to open.
        /// </summary>
        /// <returns></returns>
        public string[] GetConnectedDeviceNames()
        {
            return UsbLink.GetConnectedDeviceNames();
        }
        /// <summary>
        /// Specifies and opens the serial communication port to use.
        /// </summary>
        /// <param name="port">The serial port to use. Values in the range 1 to 256 are accepted.</param>
        public override void OpenPort(int port)
        {
            OpenPort(port, 4800);
        }

        /// <summary>
        /// Specifies and opens the serial communication port to use at the specified speed.
        /// </summary>
        /// <param name="port">Specifies the serial port to use. Values in the range 1 to 256 are accepted.</param>
        /// <param name="speed">Specifies the baud rate to use.</param>
        public override void OpenPort(int port, int speed)
        {
            if (port < 1 || port > 256)
                throw new ArgumentOutOfRangeException(ERR_PORT_RANGE);
            m_port = port;

            if (speed == 1200 || speed == 4800 || speed == 9600 || speed == 19200 || speed == 57600 || speed == 115200)
                m_speed = speed;
            else
                throw new ArgumentOutOfRangeException(ERR_BAUD_RANGE);

        }
        /// <summary>
        /// Opens the USB port (where supported). The first detected USB GPS device will be used.
        /// </summary>
        public override void OpenPort()
        {
            //set this to zero ensures usb will be selected
            m_port = 0;
        }
        /// <summary>
        /// Opens the USB port (where supported). A USB port name as returned by <see cref="GetConnectedDeviceNames"/> should be specified.
        /// </summary>
        /// <param name="port">Typically a USB port name as returned by <see cref="GetConnectedDeviceNames"/>.</param>
        public void OpenPort(string port)
        {
            m_port = 0;
            m_usbPortName = port;
        }
        /// <summary>
        /// Closes the currently open port. This can safely be called at any time.
        /// </summary>
        public override void ClosePort()
        {
            //do nothing, this is simply here to keep users happy.
        }
        /// <summary> 
        /// This method cancels any transfers either to or from the connected GPS device. PVT Data transfers are also cancelled.      
        /// </summary>      
        // <param>         
        // </param>     
        // <returns></returns>		
        public void CancelTransfer()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     Sets the module level XferAbort flag whick is read by each
            //               of the data transfer functions allowing transfers to be
            //               aborted
            //
            try
            {
                flags.TransferAbort = true;

                //TODO: needs to wait until all transfers have been aborted

                //stop any pvt stuff
                if (pvtThread != null)
                    pvtThread.Abort();

                //this.DataEventEnabled = false;
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
        }

        /// <summary>
        /// This method returns the current Data and Time as reported by the GPS device.
        /// </summary>
        public string GetDateTime()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     Gets the current date/time from the gps and populates the
            //               property
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            int iMonth = 0;
            int iDay = 0;
            int iYear = 0;
            int iHour = 0;
            int iMinute = 0;
            int iSecond = 0;

            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (!mbBusy)
                {

                    if (mobjProdInfo.ProductId == 0)
                        GetProductInfo();

                    PortOpen();

                    mbBusy = true;

                    //reset the link
                    DeviceCommand(CommandID.AbortTransfer);

                    //clear the buffer
                    ClearBuffers();

                    //get the posistion data
                    DeviceCommand(CommandID.TransferTime);

                    PData = ReceiveData();

                    if (PData.Pid == ProcessId.DateTimeData)
                    {
                        //date time
                        iMonth = (int)(PData.ByteData[0]);
                        iDay = (int)(PData.ByteData[1]);
                        iYear = ByteToShort(PData.ByteData, 2);
                        iHour = ByteToShort(PData.ByteData, 4);
                        iMinute = (int)(PData.ByteData[6]);
                        iSecond = (int)(PData.ByteData[7]);
                    }
                    else
                    {
                        throw new DataTransferException(ERR_MSG_3004);
                    }
                    mbBusy = false;
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return string.Concat(PadString(iDay.ToString(CultureInfo.InvariantCulture)), "/", PadString(iMonth.ToString(CultureInfo.InvariantCulture)), "/", PadString(iYear.ToString(CultureInfo.InvariantCulture)), " ", PadString(iHour.ToString(CultureInfo.InvariantCulture)), ":", PadString(iMinute.ToString(CultureInfo.InvariantCulture)), ":", PadString(iSecond.ToString(CultureInfo.InvariantCulture)));
        }
        /// <summary>
        /// Returns a RouteCollection object from the GPS Device. 
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetRoutes()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the Routes
        ///     RouteCollection rtes = gps.GetRoutes();
        ///
        ///     //Now we have a collection of Routes
        ///     foreach (Route rte in rtes)
        ///     {
        ///         Console.WriteLine(rte.Identifier);
        ///         Console.WriteLine(rte.Comment);
        ///
        ///         //Each Route has a collection of Waypoints
        ///         foreach (Waypoint wpt in rte.Waypoints)
        ///         {
        ///             Console.WriteLine(wpt.Latitude);
        ///             Console.WriteLine(wpt.Longitude);
        ///         }
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = rtes.ToDataSet();
        ///     dataGridView1.DataMember = "Routes";
        ///
        /// }
        /// </code>
        /// </example>
        public override RouteCollection GetRoutes()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This sub populates the module level Routes collection
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------
            //
            GpsPidData PData;
            int iRecords = 0;
            Route oRoute = null;
            //Waypoints cRouteWaypoints = null; 
            bool bA201 = false;			//only two protocols supported A200 & A201
            int lByteCount = 0;
            //byte[] bytData = null;		//used for temporary storage
            short iTmpClass = 0;		//-------------"------------
            Subclass bTmpSubClass = new Subclass();	//-------------"------------
            string sTmpIdentifier = ""; //-------------"------------
            int iPackets = 0;
            Waypoint objWaypoint = null;

            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                TraceLog.WriteMessage("Retrieving Routes From GPS", TraceLevel.Info, true);

                //reset the collection
                mcRoutes = new RouteCollection();

                //'more efficient checking for a boolean later on
                if (mcProtocolTable["Route"].ToString() == "A201")
                    bA201 = true;

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //retrieve the waypoints, first send a CommandData Xfer Waypoints
                DeviceCommand(CommandID.TransferRte);

                //check for the returned data
                PData = ReceiveData();

                //check for a 'Records' pid this indicates the start of the waypoint list
                if (PData.Pid == ProcessId.Records)
                {

                    //the first returned packet shows the no of packets to follow
                    //this refers to the number of headers + waypoints + linkdata
                    iRecords = ByteToShort(PData.ByteData, 0);

                    if (iRecords > 0) //iRecords holds the number of packets to follow NOT the number of routes
                    {
                        //'get the hdr for the first route
                        PData = ReceiveData();

                        //sort out progress
                        //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100))
                        if (Progress != null)
                        {
                            Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                        }

                        while (PData.Pid == ProcessId.RouteHeader)
                        {
                            

                            if (flags.TransferAbort)
                            {
                                DeviceCommand(CommandID.AbortTransfer);
                                TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                break;
                            }

                            //get the length of packet
                            lByteCount = PData.ByteData.Length;

                            //create a new route object
                            oRoute = new Route();

                            //copy the header info into the route object properties
                            switch (mcProtocolTable["RouteD0"].ToString())
                            {
                                case "D200":
                                    oRoute.Number = PData.ByteData[0];
                                    break;
                                case "D201":
                                    oRoute.Number = PData.ByteData[0];
                                    oRoute.Comment = ByteToString(PData.ByteData, 1, 20);
                                    break;
                                case "D202": //null terminated string
                                    oRoute.Identifier = ByteToString(PData.ByteData, 0, lByteCount, true, true);
                                    break;
                                default:
                                    throw new DataTransferException(ERR_MSG_3002);
                            }

                            //get the first route waypoint
                            PData = ReceiveData();

                            //sort out progress
                            iPackets = iPackets + 1;

                            //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100))

                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                            }

                            //Debug.WriteLine("Records:  " & CStr(iRecords))
                            //Debug.WriteLine("Packets:  " & CStr(iPackets))

                            //get the rest of the waypoints for this route
                            while (PData.Pid == ProcessId.RouteWaypointData)
                            {

                                objWaypoint = CreateWaypointObject(PData.ByteData, WaypointType.Route);

                                if (bA201)
                                {//add link data

                                    //link data is added to the waypoint following the
                                    //link packet. i.e. the first waypoint doesn't contain link data
                                    //but the subsequent waypoint would contain the data to link the two
                                    //link data does NOT follow the final data packet.

                                    //although this code block is executed for the first waypoint, the
                                    //variables are all blank so no changes are made to the waypoint object
                                    objWaypoint.RouteLinkClass = iTmpClass;
                                    objWaypoint.RouteLinkSubclass = bTmpSubClass;
                                    objWaypoint.RouteLinkIdentifier = sTmpIdentifier;

                                }

                                //add the object to the collection in the route object
                                oRoute.Waypoints.Add(objWaypoint);
                                //
                                if (bA201)
                                {
                                    //get the link packet (A200) doesn't use link packets)
                                    PData = ReceiveData();

                                    //could be the transfer complete packet or a route header
                                    if (PData.Pid == ProcessId.TransferComplete || PData.Pid == ProcessId.RouteHeader)
                                        break;

                                    //get the length of packet
                                    lByteCount = PData.ByteData.Length;

                                    //sort out progress
                                    iPackets = iPackets + 1;

                                    //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100))
                                    if (Progress != null)
                                    {
                                        Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                                    }

                                    //store the link data from this packet so that it can be
                                    //added to the next waypoint. Only one Link Data protocol
                                    //to worry about (D210)
                                    iTmpClass = ByteToShort(PData.ByteData, 0);
                                    bTmpSubClass = new Subclass(CopyByteArray(PData.ByteData, 2, 18));
                                    sTmpIdentifier = ByteToString(PData.ByteData, 20, lByteCount - 20, true, true);

                                    //Debug.WriteLine(sTmpIdentifier);
                                }

                                //get the next waypoint
                                PData = ReceiveData();

                                //sort out progress
                                iPackets = iPackets + 1;
                                //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100))
                                if (Progress != null)
                                {
                                    Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                                }

                                System.Windows.Forms.Application.DoEvents();
                            }

                            //add the route object to the routes collection if the route object
                            //is nothing then this must be the first header so dont add
                            if (oRoute != null)
                                mcRoutes.Add(oRoute);

                            System.Windows.Forms.Application.DoEvents();
                        }

                        if (!flags.TransferAbort)
                        {
                            //check for the transfer complete packet
                            if (PData.Pid != ProcessId.TransferComplete)
                                throw new DataTransferException(ERR_MSG_3001);
                        }
                        else
                        {
                            flags.TransferAbort = false;
                        }

                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3001);
                }

                //reset callers progress indicator
                //RaiseEvent Progress(0)
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }

                //				}
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return mcRoutes;
        }
        /// <summary>
        /// Returns a TrackLogCollection of TrackLog objects from the GPS Device.
        /// The SplitTracklog parameter when set to true returns multi-segment tracks
        /// as separate Tracklog objects. When set to false a multi-segment track is 
        /// returned as a single Tracklog object.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetTracklogs()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the Tracklogs
        ///     TracklogCollection trks = gps.GetTracklogs();
        ///
        ///     //Now we have a collection of Tracklogs
        ///     foreach (Tracklog trk in trks)
        ///     {
        ///         Console.WriteLine(trk.Identifier);
        ///
        ///         //Each Tracklog has a collection of Trackpoints
        ///         foreach (Trackpoint tpt in trk.Trackpoints)
        ///         {
        ///             Console.WriteLine(tpt.Latitude);
        ///             Console.WriteLine(tpt.Longitude);
        ///         }
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = trks.ToDataSet();
        ///     dataGridView1.DataMember = "Tracks";
        ///
        /// }
        /// </code>
        /// </example>
        public override TracklogCollection GetTracklogs()
        {
            return GetTracklogs(false);
        }
        /// <summary>
        /// Returns a TrackLogCollection of TrackLog objects from the GPS Device.
        /// The SplitTracklog parameter when set to true returns multi-segment tracks
        /// as separate Tracklog objects. When set to false a multi-segment track is 
        /// returned as a single Tracklog object.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetTracklogs()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the Tracklogs
        ///     TracklogCollection trks = gps.GetTracklogs();
        ///
        ///     //Now we have a collection of Tracklogs
        ///     foreach (Tracklog trk in trks)
        ///     {
        ///         Console.WriteLine(trk.Identifier);
        ///
        ///         //Each Tracklog has a collection of Trackpoints
        ///         foreach (Trackpoint tpt in trk.Trackpoints)
        ///         {
        ///             Console.WriteLine(tpt.Latitude);
        ///             Console.WriteLine(tpt.Longitude);
        ///         }
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = trks.ToDataSet();
        ///     dataGridView1.DataMember = "Tracks";
        ///
        /// }
        /// </code>
        /// </example>
        public override TracklogCollection GetTracklogs(bool splitTracklog)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT: Retrieves the GPS Tracklog data and populates the Track
            //           property collection of Track Points.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            int iRecords = 0;
            int lByteCount = 0;
            //byte[] bytData = null; //used for temporary storage
            int iPoints = 0;
            bool bA301 = false;
            PData.Pid = 0;
            PData.ByteData = null;
            string strProtocol = "";
            TracklogCollection objTrackLogs = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                //more efficient checking for a boolean later on
                //hash table could be null at this point if comms doesn't work
                strProtocol = mcProtocolTable["Track"].ToString();
                if (strProtocol == "A301" || strProtocol == "A302")
                    bA301 = true;

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //retrieve the tracks, first send a CommandData Xfer track
                DeviceCommand(CommandID.TransferTrk);

                //check for the returned data
                PData = ReceiveData();

                //create the tracklog collection
                mTrackLogs = new TracklogCollection();
                
                //check for a 'Records' pid this indicates the start of the trackpoints
                if (PData.Pid == ProcessId.Records)
                {

                    //the first returned packet shows the no of packets to follow
                    //this refers to the number of headers + Track Points
                    iRecords = ByteToShort(PData.ByteData, 0);
                    //Debug.WriteLine("Packets=" + iRecords.ToString(CultureInfo.InvariantCulture));

                    if (iRecords > 0) //iRecords holds the number of packets to follow NOT necessarily the number of trackpoints
                    {

                        //create a new tracklog objects
                        mTrackLog = new Tracklog();
                        string trackHeaderProtocol = mcProtocolTable["TrackD0"].ToString();
                        
                        if (bA301) //get the header
                        {
                            //get the hdr
                            PData = ReceiveData();

                            if (PData.Pid == ProcessId.TrackHeader)
                            {
                                PopulateTrackHeader(mTrackLog, PData);
                            }
                            else
                            {
                                throw new DataTransferException(ERR_MSG_3007);
                            }
                            //alter the counter to reflect the fact we have processed the header
                            iRecords = iRecords - 1;

                        }

                        iPoints = 1;

                        //loop here to get all the tracklogs
                        do
                        {
                            System.Windows.Forms.Application.DoEvents();

                            //get the first trackpoint
                            PData = ReceiveData();

                            while (PData.Pid == ProcessId.TrackData)
                            {
                                if (flags.TransferAbort)
                                {
                                    DeviceCommand(CommandID.AbortTransfer);
                                    TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                    break;
                                }
                                //add the track point to the collection
                                mTrackLog.Trackpoints.Add(CreateTrackPointObject(PData.ByteData));

                                //sort out progress
                                //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPoints), , 100))
                                if (Progress != null)
                                {
                                    Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPoints), 0, 100)));
                                    //									Debug.WriteLine("iRecords:=" +  iRecords.ToString(CultureInfo.InvariantCulture));
                                    //									Debug.WriteLine("iPoints:=" + iPoints.ToString(CultureInfo.InvariantCulture));
                                    //									Debug.WriteLine("Progress:=" + Limit((int)((100.00 / iRecords) * iPoints), 0, 100));
                                }

                                System.Windows.Forms.Application.DoEvents();

                                //get the next trackpoint
                                PData = ReceiveData();

                                //Debug.WriteLine("Pid=" & PData.Pid.ToString);

                                iPoints = iPoints + 1;
                            }
                            //exit the outer loop
                            if (flags.TransferAbort)
                                break;

                            if (splitTracklog)
                            {
                                objTrackLogs = SplitTrackLog(mTrackLog);
                                foreach (Tracklog objTracklog in objTrackLogs)
                                {
                                    mTrackLogs.Add(objTracklog);
                                }
                            }
                            else
                            {
                                mTrackLogs.Add(mTrackLog);
                            }

                            //there could be multiple track logs in which case a TrackHeader would appear here
                            if (PData.Pid == ProcessId.TrackHeader)
                            {
                                mTrackLog = new Tracklog();
                                PopulateTrackHeader(mTrackLog, PData);
                            }
                        }
                        while (PData.Pid == ProcessId.TrackHeader);

                        if (!flags.TransferAbort) //check for the transfer complete packet
                        {
                            if (PData.Pid != ProcessId.TransferComplete)
                                throw new DataTransferException(String.Concat(ERR_MSG_3007, " No Transfer Complete Packet"));
                        }
                        else
                        {
                            flags.TransferAbort = false;
                        }
                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3007);
                }
                //reset callers progress indicator
                //RaiseEvent Progress(0)
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }
            return mTrackLogs;

        }

        /// <summary>
        /// This method returns a Position object in containing the current Latitude and Longtidue in degrees as reported by the GPS device.
        /// </summary>
        public Position GetPosition()
        {
            Position objPos = new Position();

            try
            {
                if (!mbBusy)
                {
                    mbBusy = true;
                    objPos = GetLatLong();
                    mbBusy = false;
                }
                else
                {
                    //Debug.WriteLine("mbBusy = true");
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }

            return objPos;
        }

        /// <summary>
        /// Returns a WaypointCollection object from the GPS device.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetWaypoints()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the Waypoints
        ///     WaypointCollection wpts = gps.GetWaypoints();
        /// 
        ///     //Now we have a collection of Waypoints
        ///     foreach (Waypoint wpt in wpts)
        ///     {
        ///         Console.WriteLine(wpt.Latitude);
        ///         Console.WriteLine(wpt.Longitude);
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = wpts.ToDataSet();
        ///     dataGridView1.DataMember = "Waypoints";
        ///
        /// }
        /// </code>
        /// </example>
        public override WaypointCollection GetWaypoints()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This sub populates the module level waypoints collection
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            int iWaypoints;
            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                TraceLog.WriteMessage("Retrieving Waypoints From GPS", TraceLevel.Info, true);

                //reset the collection
                mcWayPoints = new WaypointCollection();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //retrieve the waypoints, first send a CommandData Xfer Waypoints
                DeviceCommand(CommandID.TransferWpt);

                //check for the returned data
                PData = ReceiveData();

                //check for a 'Records' pid this indicates the start of the waypoint list
                if (PData.Pid == ProcessId.Records)
                {
                    //the first returned packet shows the no of waypoints to follow
                    iWaypoints = ByteToShort(PData.ByteData, 0);

                    if (iWaypoints > 0)
                    {
                        for (int f = 1; f <= iWaypoints; f++) //has number of records to follow
                        {

                            if (flags.TransferAbort)
                            {
                                DeviceCommand(CommandID.AbortTransfer);
                                TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                break;
                            }

                            //get the next packet
                            PData = ReceiveData();

                            //make sure its a waypoint
                            if (PData.Pid == ProcessId.WaypointData)
                            {
                                mcWayPoints.Add(CreateWaypointObject(PData.ByteData, WaypointType.Waypoint));
                                //
                                //work out percentage done and raise event for the caller
                                //RaiseEvent Progress(Limit(CInt((100 / iWaypoints) * f), , 100))
                                if (Progress != null)
                                {
                                    Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iWaypoints) * f), 0, 100)));
                                }
                            }
                            else
                            {
                                throw new DataTransferException(ERR_MSG_3000);
                            }
                        }
                        if (!flags.TransferAbort)
                        {
                            //check for the transfer complete packet
                            PData = ReceiveData();
                            if (PData.Pid != ProcessId.TransferComplete)
                                throw new DataTransferException(ERR_MSG_3000);
                        }
                        else
                        {
                            //reset the cancel flag
                            flags.TransferAbort = false;
                        }
                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3000);
                }

                //set the callers progress indicator to 0
                //RaiseEvent Progress(0)
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return mcWayPoints;
        }

        /// <summary>
        /// Accepts a Waypoint object and transfers it to the connected GPS device.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void SetWaypoint()
        /// {
        ///
        ///     GarminDevice gps = new GarminDevice();
        /// 
        ///     Waypoint newWpt = null;
        ///
        ///     //Create a Waypoint
        ///     newWpt = new Waypoint();
        ///     newWpt.Identifier = "Test01";
        ///     newWpt.Latitude = 53.9608166666667;
        ///     newWpt.Longitude = -2.02225;
        ///     newWpt.Comment = "Test 1";
        ///
        ///     //Transfer the Waypoint to the GPS device
        ///     gps.SetWaypoint(newWpt);
        ///
        /// }
        ///</code>
        ///</example>
        public override bool SetWaypoint(Waypoint waypoint)
        {
            WaypointCollection colWaypoints = new WaypointCollection();
            colWaypoints.Add(waypoint);
            return SetWaypoints(colWaypoints);
        }
        /// <summary>
        /// Accepts a WaypointCollection object and transfers it to the connected GPS device. 
        /// </summary>
        ///<example>C#
        /// <code>
        /// private void SetWaypoints()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        /// 
        ///     WaypointCollection newWpts = new WaypointCollection();
        ///     Waypoint newWpt = null;
        ///
        ///     //Create some Waypoints
        ///
        ///     //Waypoint 1
        ///     newWpt = new Waypoint();
        ///     newWpt.Identifier = "Test01";
        ///     newWpt.Latitude = 53.9608166666667;
        ///     newWpt.Longitude = -2.02225;
        ///     newWpt.Comment = "Test 1";
        ///
        ///     //Add the new waypoint to a Waypoint collection
        ///     newWpts.Add(newWpt);
        ///
        ///     //Waypoint 2
        ///     newWpt = new Waypoint();
        ///     newWpt.Identifier = "Test02";
        ///     newWpt.Latitude = 190.0;
        ///     newWpt.Longitude = 1180.0;
        ///     newWpt.Comment = "Test 2";
        ///
        ///     //Add the new Waypoint to a Waypoint collection
        ///     newWpts.Add(newWpt);
        ///
        ///     //Transfer the Waypoints to the GPS device
        ///     gps.SetWaypoints(newWpts);
        ///
        /// }
        /// </code>
        /// </example>
        public override bool SetWaypoints(WaypointCollection waypoints)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            //Waypoint oWaypoint;
            int iWaypoints;
            int f = 0;

            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                if (waypoints != null)
                {

                    TraceLog.WriteMessage("Sending Waypoints to GPS", TraceLevel.Info, true);

                    //reset the link
                    DeviceCommand(CommandID.AbortTransfer);

                    //clear the buffer
                    ClearBuffers();

                    //create the first packet
                    PData.Pid = ProcessId.Records;
                    PData.ByteData = ByteConcat(PData.ByteData, ShortToByte((short)waypoints.Count));

                    if (TransmitData(PData))
                    {
                        iWaypoints = waypoints.Count;

                        foreach (Waypoint oWaypoint in waypoints)
                        {

                            f = f + 1;
                            //
                            if (flags.TransferAbort)
                            {
                                DeviceCommand(CommandID.AbortTransfer);
                                TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                break;
                            }

                            PData.Pid = ProcessId.WaypointData;
                            PData.ByteData = CreateWaypointArray(oWaypoint);

                            if (!TransmitData(PData))
                                throw new DataTransferException(ERR_MSG_3000);

                            //work out percentage done and raise event for the caller
                            //RaiseEvent Progress(Limit((100 / iWaypoints) * f, , 100))
                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iWaypoints) * f), 0, 100)));
                            }
                        }

                        if (!flags.TransferAbort)
                        {
                            //send transfer complete packet
                            PData.Pid = ProcessId.TransferComplete;
                            PData.ByteData = ShortToByte((short)CommandID.TransferWpt);

                            if (!TransmitData(PData))
                                throw new DataTransferException(ERR_MSG_3000);
                        }
                        else
                        {
                            //reset the cancel flag
                            flags.TransferAbort = false;
                        }

                        //RaiseEvent Progress(0)
                        if (Progress != null)
                        {
                            Progress(this, new ProgressEventArgs(0));
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            //if we get here then all is well
            return true;
        }
        /// <summary>
        /// Accepts a Route object and transfers it to the connected GPS device.
        /// A value of True is returned if the transfer was successful.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void SetRoute()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        /// 
        ///     Waypoint wpt;
        ///     Route rte;
        ///     WaypointCollection wpts;
        ///
        ///     //Create the collection of Waypoints for the Route
        ///     wpts = new WaypointCollection();
        ///
        ///     //Waypoint 1
        ///     wpt = new Waypoint();
        ///     wpt.Identifier = "RT01";
        ///     wpt.Latitude = 1.54;
        ///     wpt.Longitude = 0.2;
        ///
        ///     //Add the new Waypoint to the Waypoint Collection
        ///     wpts.Add(wpt);
        ///
        ///     //Waypoint 2
        ///     wpt = new Waypoint();
        ///     wpt.Identifier = "RT02";
        ///     wpt.Latitude = 1.53;
        ///     wpt.Longitude = 0.22;
        ///
        ///     //Add the new Waypoint to the Waypoint Collection
        ///     wpts.Add(wpt);
        ///
        ///     //Create and populate the Route
        ///     rte = new Route();
        ///     rte.Number = 2;
        ///     rte.Comment = "Hello Route";
        ///     rte.Waypoints = wpts;
        ///
        ///     //Transfer the Route to the GPS device
        ///     gps.SetRoute(rte);
        ///
        /// }
        /// </code>
        /// </example>
        public override bool SetRoute(Route route)
        {
            RouteCollection colRoutes = new RouteCollection();
            colRoutes.Add(route);
            return SetRoutes(colRoutes);
        }
        /// <summary>
        /// Accepts a RouteCollection object and transfers it to the connected GPS device. 
        /// A value of True is returned if the transfer was successful.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void SetRoutes()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        /// 
        ///     Waypoint wpt;
        ///     Route rte;
        ///     RouteCollection rtes;
        ///     WaypointCollection wpts;
        ///
        ///     //Create the collection of Waypoints for the Route
        ///     wpts = new WaypointCollection();
        ///
        ///     //Waypoint 1
        ///     wpt = new Waypoint();
        ///     wpt.Identifier = "RT01";
        ///     wpt.Latitude = 1.54;
        ///     wpt.Longitude = 0.2;
        ///
        ///     //Add the new Waypoint to the Waypoint Collection
        ///     wpts.Add(wpt);
        ///
        ///     //Waypoint 2
        ///     wpt = new Waypoint();
        ///     wpt.Identifier = "RT02";
        ///     wpt.Latitude = 1.53;
        ///     wpt.Longitude = 0.22;
        ///
        ///     //Add the new Waypoint to the Waypoint Collection
        ///     wpts.Add(wpt);
        ///
        ///     //Create and populate the Route
        ///     rte = new Route();
        ///     rte.Number = 2;
        ///     rte.Comment = "Hello Route";
        ///     rte.Waypoints = wpts;
        ///
        ///     //Create a routes collection 
        ///     //Typically a single Route would be transferred using SetRoute
        ///     rtes = new RouteCollection();
        ///     rtes.Add(rte);
        ///
        ///     //Transfer the Routes to the GPS device
        ///     gps.SetRoutes(rtes);
        ///
        /// }
        /// </code>
        /// </example>
        public override bool SetRoutes(RouteCollection routes)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            //Route oRoute = null; 
            //Waypoint oWaypoint = null;
            int iRecords = 0;
            int iLinks = 0;
            int iHeaders = 0;
            int iWaypoints = 0;
            int iPackets = 0;
            bool linkProtocol = false;
            bool bFirst = false;

            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                if (routes != null)
                {
                    TraceLog.WriteMessage("Sending Routes to GPS", TraceLevel.Info, true);

                    if (mcProtocolTable["Route"].ToString() == "A201")
                        linkProtocol = true;

                    //calculate packets to send headers, waypoints, link data
                    //first the number of route headers
                    iHeaders = routes.Count;

                    //then the waypoints
                    foreach (Route oRoute in routes)
                    {
                        iWaypoints = iWaypoints + oRoute.Waypoints.Count;
                    }

                    //then the link data packets (links only exist for protocol A201
                    if (linkProtocol)
                        iLinks = iWaypoints - (routes.Count);

                    //total records
                    iRecords = iHeaders + iWaypoints + iLinks;

                    //reset the link
                    DeviceCommand(CommandID.AbortTransfer);

                    //clear the buffer
                    ClearBuffers();

                    //create the first packet
                    PData.Pid = ProcessId.Records;
                    PData.ByteData = ByteConcat(PData.ByteData, ShortToByte((short)iRecords));

                    if (!TransmitData(PData))
                        throw new DataTransferException(ERR_MSG_3001);

                    foreach (Route oRoute in routes)
                    {
                        if (flags.TransferAbort)
                        {
                            DeviceCommand(CommandID.AbortTransfer);
                            TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                            break;
                        }

                        //copy the header info into the next packet
                        switch (mcProtocolTable["RouteD0"].ToString())
                        {
                            case "D200":
                                PData.Pid = ProcessId.RouteHeader;
                                PData.ByteData = ShortToByte(oRoute.Number, true);
                                //oRoute.Number = PData.ByteData(0);
                                break;
                            case "D201":
                                PData.Pid = ProcessId.RouteHeader;
                                PData.ByteData = ShortToByte(oRoute.Number, true);
                                PData.ByteData = ByteConcat(PData.ByteData, StringToByte(Pad(CharacterSetTrim(oRoute.Comment, CharSet.RouteComment), 20)));
                                break;
                            case "D202": //null terminated string
                                PData.Pid = ProcessId.RouteHeader;
                                PData.ByteData = StringToByte(CharacterSetTrim(oRoute.Identifier, CharSet.RouteIdentifier), true);
                                break;
                            default:
                                throw new DataTransferException(ERR_MSG_3002);
                        }

                        if (!TransmitData(PData))
                            throw new DataTransferException(ERR_MSG_3001);

                        //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100))
                        if (Progress != null)
                        {
                            Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                        }

                        iPackets = iPackets + 1;

                        //used to trigger link packets later on
                        bFirst = true;

                        //now send each of the waypoints
                        foreach (Waypoint oWaypoint in oRoute.Waypoints)
                        {
                            if (flags.TransferAbort)
                            {
                                DeviceCommand(CommandID.AbortTransfer);
                                TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                break;
                            }

                            if (linkProtocol) //dont do this before the first waypoint has gone
                            {
                                //send the link data
                                //link data is added to the waypoint following the
                                //link packet. i.e. the first waypoint object doesn't contain link data
                                //but the subsequent waypoint objects could contain the data to link the two

                                if (!bFirst)
                                {
                                    switch (mcProtocolTable["RouteD2"].ToString())
                                    {
                                        case "D210":
                                            PData.Pid = ProcessId.RouteLinkData;
                                            PData.ByteData = ShortToByte(oWaypoint.RouteLinkClass);
                                            PData.ByteData = ByteConcat(PData.ByteData, PadByte(oWaypoint.RouteLinkSubclass.ToByteArray(),18)); //always 18 bytes on Route protocols
                                            PData.ByteData = ByteConcat(PData.ByteData, StringToByte(oWaypoint.RouteLinkIdentifier, true));
                                            break;
                                        default:
                                            throw new DataTransferException(ERR_MSG_3002);

                                    }

                                    if (!TransmitData(PData))
                                        throw new DataTransferException(ERR_MSG_3001);

                                    //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100));
                                    if (Progress != null)
                                    {
                                        Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                                    }
                                    iPackets = iPackets + 1;
                                }
                            }
                            PData.Pid = ProcessId.RouteWaypointData;
                            PData.ByteData = CreateWaypointArray(oWaypoint);

                            if (!TransmitData(PData))
                                throw new DataTransferException(ERR_MSG_3001);

                            //RaiseEvent Progress(Limit(CInt((100 / iRecords) * iPackets), , 100));
                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets), 0, 100)));
                            }
                            iPackets = iPackets + 1;

                            bFirst = false;
                        }
                    }
                    if (!flags.TransferAbort)
                    {
                        //send transfer complete packet
                        PData.Pid = ProcessId.TransferComplete;
                        PData.ByteData = ShortToByte((short)CommandID.TransferRte);
                        if (!TransmitData(PData))
                            throw new DataTransferException(ERR_MSG_3000);
                    }
                    else
                    {
                        //reset the cancel flag
                        flags.TransferAbort = false;
                    }

                    //RaiseEvent Progress(0)
                    if (Progress != null)
                    {
                        Progress(this, new ProgressEventArgs(0));
                    }
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }
            //if we get here then all is well
            return true;
        }

        private void PortOpen()
        {
            bool pvtDataRunning;

            try
            {
                //determine status of pvt
                //lock (syncLock)
                //{
                //    //readWriteLock.AcquireWriterLock(-1);
                //    pvtDataRunning = mbPVTRunning;
                //    //readWriteLock.ReleaseWriterLock();
                //}

                //could be running PVT data
       
                if (!flags.PvtRunning)
                {
                    //m_port will be zero if not been set
                    if (m_port == 0)
                    {
                        mbUSBTransport = true;

                        //if no portname has been specified then get the first usb device
                        if (m_usbPortName.Length == 0)
                        {
                            string[] devices = UsbLink.GetConnectedDeviceNames();

                            if (devices.Length > 0)
                            {
                                m_usbPortName = devices[0];
                            }
                        }
                        //port name could still be empty if no devices are connected
                        mUSB.PortOpen(m_usbPortName);
                    }
                    else
                    {
                        mbUSBTransport = false;

                        //close any previously opened comm port
                        mLink.PortClose();

                        //re-open the specified port
                        mLink.PortOpen(m_port, m_speed);

                    }
                }
                else
                {
                    throw new PortException(ERR_MSG_3016);
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new PortException(ex.Message, ex);
            }

        }
        /// <summary>
        /// Closes the communications port in use.
        /// </summary>
        private void PortClose()
        {
            try
            {
                if (m_port == 0)
                {
                    mUSB.PortClose();
                }
                else
                {
                    //close any previously opened comm port
                    mLink.PortClose();
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new PortException(ex.Message, ex);
            }
        }
        /// <summary>
        /// This method powers down the connected GPS device.
        /// </summary>
        public void PowerOff()
        {
            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                TraceLog.WriteMessage("Power Down Initiated");
                DeviceCommand((CommandID.TurnOffPower));
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }
        }
        /// <summary>
        /// Returns a WaypointCollection object representing the 
        /// Proximity Waypoints retrieved from the GPS device. 
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetProximityWaypoints()
        /// {
        ///     //Create a new device
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the Waypoints
        ///     WaypointCollection wpts = gps.GetProximityWaypoints();
        ///
        ///     //Now we have a collection of Proximity Waypoints
        ///     foreach (Waypoint wpt in wpts)
        ///     {
        ///         Console.WriteLine(wpt.Latitude);
        ///         Console.WriteLine(wpt.Longitude);
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = wpts.ToDataSet();
        ///     dataGridView1.DataMember = "Waypoints";
        /// }
        /// </code>
        /// </example>
        public WaypointCollection GetProximityWaypoints()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This sub populates the module level Proximity Waypoints
            //               collection.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            short iWaypoints = 0;
            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                //reset the collection
                mcProximityWaypoints = new WaypointCollection();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //retrieve the waypoints, first send a CommandData Xfer Waypoints
                DeviceCommand(CommandID.TransferPrx);

                //check for the returned data
                PData = ReceiveData();

                //check for a 'Records' pid this indicates the start of the waypoint list
                if (PData.Pid == ProcessId.Records)
                {
                    //the first returned packet shows the no of waypoints to follow
                    iWaypoints = ByteToShort(PData.ByteData, 0);

                    if (iWaypoints > 0)
                    {
                        for (int f = 1; f <= iWaypoints; f++) //has number of records to follow
                        {
                            if (flags.TransferAbort)
                            {
                                DeviceCommand(CommandID.AbortTransfer);
                                TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                break;
                            }

                            //get the next packet
                            PData = ReceiveData();

                            //make sure its a waypoint
                            if (PData.Pid == ProcessId.ProximityWaypointData)
                            {
                                //add the object to the collection
                                mcProximityWaypoints.Add(CreateWaypointObject(PData.ByteData, WaypointType.Proximity));

                                //work out percentage done and raise event for the caller
                                //RaiseEvent Progress(Limit(CInt((100 / iWaypoints) * f), , 100))
                                if (Progress != null)
                                {
                                    Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iWaypoints) * f), 0, 100)));
                                }
                            }
                            else
                            {
                                throw new DataTransferException(ERR_MSG_3006);
                            }
                        }

                        if (!flags.TransferAbort)
                        {
                            //check for the transfer complete packet
                            PData = ReceiveData();
                            if (PData.Pid != ProcessId.TransferComplete)
                                throw new DataTransferException(ERR_MSG_3006);
                        }
                        else
                        {
                            flags.TransferAbort = false;
                        }
                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3006);
                }

                //set the callers progress indicator to 0
                //RaiseEvent Progress(0)
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
                //				}			
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return mcProximityWaypoints;
        }

        //		public void Refresh()
        //		{
        //			//-------------------------------------------------------------------------
        //			// ABSTRACT:     Flags all properties to refresh the next time they are
        //			//               accessed.
        //			//
        //			// PARAMETERS:
        //			//
        //			// RETURNS:
        //			//
        //			// DESCRIPTION:
        //			//
        //			//-------------------------------------------------------------------------
        //			//
        //			try
        //			{
        //				//set the flags
        //				mbRefreshPD = true;
        //				mbRefreshRTE = true;
        //				mbRefreshWPT = true;
        //				mbRefreshTRK = true;
        //				mbRefreshPRXWPT = true;
        //				mbRefreshALM = true;
        //			}
        //			catch(Exception e)
        //			{
        //				TraceLog.WriteError(e);
        //				throw e);
        //			}
        //		}
        /// <summary>
        /// This method causes the GPS device to transmit Position, Velocity and 
        /// Time data once per second. GPSLibrary fires the PVTData event and 
        /// presents the data as a string. This mode of operation is provided 
        /// as a convienient alternative to the NMEA data protocol.
        /// </summary>
        /// <remarks>
        /// See Device.PVTData Event for full details.
        /// </remarks>
        public void PvtDataStart()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     Starts the Position Velocity Time data
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            PData.Pid = 0;
            PData.ByteData = null;
            bool pvtDataRunning;

            try
            {
                //lock (syncLock)
                //{
                //    pvtDataRunning = mbPVTRunning;
                //}

                if (!flags.PvtRunning)
                {
                    if (mobjProdInfo.ProductId == 0)
                        GetProductInfo();

                    //start the pvtReceive thread
                    pvtThread = new Thread(new ThreadStart(this.PvtDataThread));
                    pvtThread.Name = "pvtThread";
                    pvtThread.Priority = ThreadPriority.Normal;
                    pvtThread.Start();

                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
        }
        //		public void ReceiverMeasurementStart()
        //		{
        //			//-------------------------------------------------------------------------
        //			// ABSTRACT:     Starts the Receiver Measurement data
        //			//
        //			// PARAMETERS:
        //			//
        //			// RETURNS:
        //			//
        //			// DESCRIPTION:
        //			//
        //			//-------------------------------------------------------------------------
        //
        //			GpsPidData PData;
        //			PData.Pid = 0;
        //			PData.ByteData = null;
        //          bool pvtDataRunning
        //
        //			try
        //			{
        //				
        //              lock (syncLock)
        //              {
        //                  pvtDataRunning = mbPVTRunning;
        //              }
        //
        //				if( !pvtDataRunning )
        //				{
        //					if(mobjProdInfo.ProductId == 0)
        //						GetProductInfo();
        //
        //					//start the pvtReceive thread
        //					pvtThread = new Thread(new ThreadStart(this.ReceiverMeasurementDataThread));
        //					pvtThread.Name = "pvtThread";
        //					pvtThread.Priority = ThreadPriority.Normal;
        //					pvtThread.Start();
        //
        //				}
        //			}
        //			catch(Exception e)
        //			{
        //				TraceLog.WriteError(e);
        //				throw new Exceptions.GeneralException(e.Message, e);
        //			}		
        //		}			
        //		/// <summary>
        //		/// This property returns a ProductInfo object which contains details of the connected GPS Device.
        //		/// </summary>
        //		public ProductInfo ProductInfo
        //		{
        //			get
        //			{
        //				return GetProductData();
        //			}
        //		}


        /// <summary>
        /// Returns a AlmanacCollection object from the GPS Device.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetAlmanacs()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///    //Retrieve the Almanacs
        ///    AlmanacCollection almacs = gps.GetAlmanacs();
        ///
        ///    //Now we have a collection of Almanacs
        ///    foreach (Almanac almc in almacs)
        ///    {
        ///        Console.WriteLine(almc.SatelliteId);
        ///        Console.WriteLine(almc.Health);
        ///    }
        ///
        ///    //Alternatively display the results in a DataGridView
        ///    dataGridView1.DataSource = almacs.ToDataSet();
        ///    dataGridView1.DataMember = "Almanacs";
        ///
        /// }
        /// </code>
        /// </example>
        public override AlmanacCollection GetAlmanacs()
        {
            //    Const vbProcedure As String = vbModule & ".GetAlmanacs"
            //-------------------------------------------------------------------------
            // ABSTRACT: Retrieves the GPS Almanac data and populates the Almanac
            //           property collection of satellites.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------
            //
            GpsPidData PData;
            int iRecords = 0;
            Almanac oAlmanac = null;
            //string sTemp = "";
            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                mcAlmanacs = new AlmanacCollection();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //only one protocol (A500)
                DeviceCommand(CommandID.TransferAlm);

                PData = ReceiveData();

                if (PData.Pid == ProcessId.Records)
                {

                    //retrieve the no of records
                    iRecords = ByteToShort(PData.ByteData, 0);

                    //Debug.WriteLine("Almanac Records = " + iRecords.ToString(CultureInfo.InvariantCulture));

                    //loop through the records getting the data
                    for (short f = 1; f <= iRecords; f++)
                    {

                        if (flags.TransferAbort)
                        {
                            DeviceCommand(CommandID.AbortTransfer);
                            TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                            break;
                        }

                        PData = ReceiveData();
                        if (PData.Pid == ProcessId.AlmanacData)
                        {
                            //create an almanac object
                            oAlmanac = new Almanac();

                            switch (mcProtocolTable["AlmanacD0"].ToString())
                            {
                                case "D500":
                                    oAlmanac.WeekNumber = ByteToShort(PData.ByteData, 0);
                                    oAlmanac.ReferenceTime = ByteToSingle(PData.ByteData, 2);
                                    oAlmanac.ClockCorrectionAF0 = ByteToSingle(PData.ByteData, 6);
                                    oAlmanac.ClockCorrectionAF1 = ByteToSingle(PData.ByteData, 10);
                                    oAlmanac.Eccentricity = ByteToSingle(PData.ByteData, 14);
                                    oAlmanac.SqrRootSemiMajorAxis = ByteToSingle(PData.ByteData, 18);
                                    oAlmanac.MeanAnomaly = ByteToSingle(PData.ByteData, 22);
                                    oAlmanac.Perigee = ByteToSingle(PData.ByteData, 26);
                                    oAlmanac.RightAscension = ByteToSingle(PData.ByteData, 30);
                                    oAlmanac.RightAscensionRate = ByteToSingle(PData.ByteData, 34);
                                    oAlmanac.Inclination = ByteToSingle(PData.ByteData, 36);
                                    oAlmanac.SatelliteId = f;
                                    if (oAlmanac.WeekNumber < 0)
                                        oAlmanac.Health = -1;
                                    break;
                                case "D501":
                                    oAlmanac.WeekNumber = ByteToShort(PData.ByteData, 0);
                                    oAlmanac.ReferenceTime = ByteToSingle(PData.ByteData, 2);
                                    oAlmanac.ClockCorrectionAF0 = ByteToSingle(PData.ByteData, 6);
                                    oAlmanac.ClockCorrectionAF1 = ByteToSingle(PData.ByteData, 10);
                                    oAlmanac.Eccentricity = ByteToSingle(PData.ByteData, 14);
                                    oAlmanac.SqrRootSemiMajorAxis = ByteToSingle(PData.ByteData, 18);
                                    oAlmanac.MeanAnomaly = ByteToSingle(PData.ByteData, 22);
                                    oAlmanac.Perigee = ByteToSingle(PData.ByteData, 26);
                                    oAlmanac.RightAscension = ByteToSingle(PData.ByteData, 30);
                                    oAlmanac.RightAscensionRate = ByteToSingle(PData.ByteData, 34);
                                    oAlmanac.Inclination = ByteToSingle(PData.ByteData, 38);
                                    oAlmanac.Health = PData.ByteData[42];
                                    oAlmanac.SatelliteId = f;
                                    break;
                                case "D550":
                                    oAlmanac.SatelliteId = PData.ByteData[0];
                                    oAlmanac.WeekNumber = ByteToShort(PData.ByteData, 1);
                                    oAlmanac.ReferenceTime = ByteToSingle(PData.ByteData, 3);
                                    oAlmanac.ClockCorrectionAF0 = ByteToSingle(PData.ByteData, 7);
                                    oAlmanac.ClockCorrectionAF1 = ByteToSingle(PData.ByteData, 11);
                                    oAlmanac.Eccentricity = ByteToSingle(PData.ByteData, 15);
                                    oAlmanac.SqrRootSemiMajorAxis = ByteToSingle(PData.ByteData, 19);
                                    oAlmanac.MeanAnomaly = ByteToSingle(PData.ByteData, 23);
                                    oAlmanac.Perigee = ByteToSingle(PData.ByteData, 27);
                                    oAlmanac.RightAscension = ByteToSingle(PData.ByteData, 31);
                                    oAlmanac.RightAscensionRate = ByteToSingle(PData.ByteData, 35);
                                    oAlmanac.Inclination = ByteToSingle(PData.ByteData, 37);
                                    if (oAlmanac.WeekNumber < 0)
                                        oAlmanac.Health = -1;
                                    break;
                                default:
                                    throw new DataTransferException(ERR_MSG_3002);
                            }
                            mcAlmanacs.Add(oAlmanac);
                            //work out percentage done and raise event for the caller
                            //RaiseEvent Progress(Limit((100 / iRecords) * f, , 100))
                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * f), 0, 100)));
                            }
                        }
                        else
                        {
                            throw new DataTransferException(ERR_MSG_3005);
                        }
                    }
                    if (!flags.TransferAbort)
                    {
                        //check for the transfer complete packet
                        PData = ReceiveData();
                        if (PData.Pid != ProcessId.TransferComplete)
                            throw new DataTransferException(ERR_MSG_3005);
                    }
                    else
                    {
                        flags.TransferAbort = false;
                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3005);
                }

                //reset the callers progress indicator
                //RaiseEvent Progress(0);
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
                //				}			
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return mcAlmanacs;
        }

        /// <summary>
        /// Returns a FlightBookCollection object from the GPS Device.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetFlightBooks()
        /// {
        ///     //Create a new device
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the FilghtBooks
        ///     FlightBookCollection fbks = gps.GetFlightBooks();
        ///
        ///     //Now we have a collection of FilghtBooks
        ///     foreach (FlightBook fbk in fbks)
        ///     {
        ///         Console.WriteLine(fbk.AircraftId);
        ///         Console.WriteLine(fbk.Distance);
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = fbks.ToDataSet();
        ///     dataGridView1.DataMember = "FlightBooks";
        /// }
        /// </code>
        /// </example>
        public FlightBookCollection GetFlightBooks()
        {
            GpsPidData PData;
            int iRecords = 0;
            FlightBook oFlightBook = null;
            //string sTemp = "";
            PData.Pid = 0;
            PData.ByteData = null;
            int intData = 0;
            string strText = "";

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                mcFlightBooks = new FlightBookCollection();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                mLink.ClearBuffer();

                //only one protocol (A500)
                DeviceCommand(CommandID.FlightBookTransfer);

                PData = mLink.ReceiveData();

                if (PData.Pid == ProcessId.Records)
                {

                    //retrieve the no of records
                    iRecords = ByteToShort(PData.ByteData, 0);

                    //Debug.WriteLine("FlightBook Records = " + iRecords.ToString(CultureInfo.InvariantCulture));

                    //loop through the records getting the data
                    for (short f = 1; f <= iRecords; f++)
                    {

                        if (flags.TransferAbort)
                        {
                            DeviceCommand(CommandID.AbortTransfer);
                            TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                            break;
                        }

                        PData = mLink.ReceiveData();
                        if (PData.Pid == ProcessId.FlightBookRecord)
                        {
                            //create an FlightBook object
                            oFlightBook = new FlightBook();

                            switch (mcProtocolTable["FlightBookD0"].ToString())
                            {
                                case "D650":
                                    oFlightBook.TakeoffTime = ByteToInt(PData.ByteData, 0);
                                    oFlightBook.LandingTime = ByteToInt(PData.ByteData, 4);
                                    oFlightBook.TakeoffLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 8));
                                    oFlightBook.TakeoffLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 12));
                                    oFlightBook.LandingLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 16));
                                    oFlightBook.LandingLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 20));
                                    oFlightBook.NightTime = ByteToInt(PData.ByteData, 24);
                                    oFlightBook.NumberOfLandings = ByteToInt(PData.ByteData, 28);
                                    oFlightBook.MaximumSpeed = ByteToSingle(PData.ByteData, 32);
                                    oFlightBook.MaximumAltitude = ByteToSingle(PData.ByteData, 36);
                                    oFlightBook.Distance = ByteToSingle(PData.ByteData, 40);

                                    if (PData.ByteData[44] == 0)// 'false = 0 True = non-zero
                                    {
                                        oFlightBook.CrossCountry = false;
                                    }
                                    else
                                    {
                                        oFlightBook.CrossCountry = true;
                                    }
                                    //get the remaining string properties as a single string
                                    int intByteCount = PData.ByteData.GetLength(0);
                                    strText = ByteToString(PData.ByteData, 45, intByteCount - 45, false, true);

                                    //split the string into its component parts, could use the split command here
                                    //but the subscript errors it can produce make it a waste of time
                                    intData = strText.IndexOf("\0");
                                    if (intData > 0)
                                        oFlightBook.DepartureName = strText.Substring(0, intData);
                                    else if (strText.Length > 0)
                                        oFlightBook.DepartureName = strText;

                                    strText = strText.Substring(intData + 1);
                                    intData = strText.IndexOf("\0");
                                    if (intData > 0)
                                        oFlightBook.DepartureIdentity = strText.Substring(0, intData);

                                    strText = strText.Substring(intData + 1);
                                    intData = strText.IndexOf("\0");
                                    if (intData > 0)
                                        oFlightBook.ArrivalName = strText.Substring(0, intData);

                                    strText = strText.Substring(intData + 1);
                                    intData = strText.IndexOf("\0");
                                    if (intData > 0)
                                        oFlightBook.ArrivalIdentity = strText.Substring(0, intData);

                                    strText = strText.Substring(intData + 1);
                                    intData = strText.IndexOf("\0");
                                    if (intData > 0)
                                        oFlightBook.AircraftId = strText.Substring(0, intData);

                                    break;

                                default:
                                    throw new DataTransferException(ERR_MSG_3002);
                            }

                            mcFlightBooks.Add(oFlightBook);
                            //work out percentage done and raise event for the caller
                            //RaiseEvent Progress(Limit((100 / iRecords) * f, , 100))
                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * f), 0, 100)));
                            }
                        }
                        else
                        {
                            throw new DataTransferException(ERR_MSG_3014);
                        }
                    }
                    if (!flags.TransferAbort)
                    {
                        //check for the transfer complete packet
                        PData = mLink.ReceiveData();
                        if (PData.Pid != ProcessId.TransferComplete)
                            throw new DataTransferException(ERR_MSG_3014);
                    }
                    else
                    {
                        flags.TransferAbort = false;
                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3014);
                }

                //reset the callers progress indicator
                //RaiseEvent Progress(0);
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
                //				}			
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return mcFlightBooks;
        }

        /// <summary>
        /// Returns a LapCollection object from the GPS Device.
        /// </summary>
        /// <example>C#
        /// <code>
        /// private void GetLaps()
        /// {
        ///     //Create a new device
        ///     GarminDevice gps = new GarminDevice();
        ///
        ///     //Retrieve the Laps
        ///     LapCollection laps = gps.GetLaps();
        ///
        ///     //Now we have a collection of Laps
        ///     foreach (Lap lap in laps)
        ///     {
        ///         Console.WriteLine(lap.Calories);
        ///         Console.WriteLine(lap.TotalTime);
        ///     }
        ///
        ///     //Alternatively display the results in a DataGridView
        ///     dataGridView1.DataSource = laps.ToDataSet();
        ///     dataGridView1.DataMember = "Laps";
        ///
        /// }
        /// </code>
        /// </example>
        public LapCollection GetLaps()
        {
            GpsPidData PData;
            int iRecords = 0;
            Lap oLap = null;
            //string sTemp = "";
            PData.Pid = 0;
            PData.ByteData = null;
            //			int intData = 0;
            //			string strText = "";

            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                mcLaps = new LapCollection();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //DeviceCommand(CommandID.TransferLaps);
                DeviceCommand(CommandID.TransferLaps);

                PData = ReceiveData();

                if (PData.Pid == ProcessId.Records)
                {

                    //retrieve the no of records
                    iRecords = ByteToShort(PData.ByteData, 0);

                    //Debug.WriteLine("Lap Records = " + iRecords.ToString(CultureInfo.InvariantCulture));

                    //loop through the records getting the data
                    for (short f = 1; f <= iRecords; f++)
                    {

                        if (flags.TransferAbort)
                        {
                            DeviceCommand(CommandID.AbortTransfer);
                            TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                            break;
                        }

                        PData = ReceiveData();

                        if (PData.Pid == ProcessId.LapData)
                        {
                            //create an FlightBook object
                            oLap = new Lap();

                            switch (mcProtocolTable["LapD0"].ToString())
                            {
                                case "D906":
                                    oLap.StartTime = ByteToInt(PData.ByteData, 0);
                                    oLap.TotalTime = ByteToInt(PData.ByteData, 4);
                                    oLap.TotalDistance = ByteToSingle(PData.ByteData, 8);
                                    oLap.BeginLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 12));
                                    oLap.BeginLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 16));
                                    oLap.EndLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 20));
                                    oLap.EndLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 24));
                                    oLap.Calories = ByteToShort(PData.ByteData, 28);
                                    oLap.TrackIndex = PData.ByteData[30];
                                    oLap.Unused1 = 0;
                                    break;

                                case "D1001":
                                    oLap.Index = ByteToInt(PData.ByteData, 0);
                                    oLap.StartTime = ByteToInt(PData.ByteData, 4);
                                    oLap.TotalTime = ByteToInt(PData.ByteData, 8);
                                    oLap.TotalDistance = ByteToSingle(PData.ByteData, 12);
                                    oLap.MaxSpeed = ByteToSingle(PData.ByteData, 16);
                                    oLap.BeginLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 20));
                                    oLap.BeginLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 24));
                                    oLap.EndLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 28));
                                    oLap.EndLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 32));
                                    oLap.Calories = ByteToShort(PData.ByteData, 36);
                                    oLap.AverageHeartRate = PData.ByteData[38];
                                    oLap.MaxHeartRate = PData.ByteData[39];
                                    oLap.Intensity = PData.ByteData[40];
                                    break;

                                case "D1011":
                                    oLap.Index = ByteToInt(PData.ByteData, 0);
                                    //bytes 2-3 are unused
                                    oLap.StartTime = ByteToInt(PData.ByteData, 4);
                                    oLap.TotalTime = ByteToInt(PData.ByteData, 8);
                                    oLap.TotalDistance = ByteToSingle(PData.ByteData, 12);
                                    oLap.MaxSpeed = ByteToSingle(PData.ByteData, 16);
                                    oLap.BeginLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 20));
                                    oLap.BeginLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 24));
                                    oLap.EndLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 28));
                                    oLap.EndLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 32));
                                    oLap.Calories = ByteToShort(PData.ByteData, 36);
                                    oLap.AverageHeartRate = PData.ByteData[38];
                                    oLap.MaxHeartRate = PData.ByteData[39];
                                    oLap.Intensity = PData.ByteData[40];
                                    oLap.AverageCadence = PData.ByteData[41];
                                    oLap.TriggerMethod = PData.ByteData[42];
                                    break;

                                case "D1015":
                                    oLap.Index = ByteToInt(PData.ByteData, 0);
                                    //bytes 2-3 are unused
                                    oLap.StartTime = ByteToInt(PData.ByteData, 4);
                                    oLap.TotalTime = ByteToInt(PData.ByteData, 8);
                                    oLap.TotalDistance = ByteToSingle(PData.ByteData, 12);
                                    oLap.MaxSpeed = ByteToSingle(PData.ByteData, 16);
                                    oLap.BeginLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 20));
                                    oLap.BeginLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 24));
                                    oLap.EndLatitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 28));
                                    oLap.EndLongitude = SemicircleToDegrees(ByteToInt(PData.ByteData, 32));
                                    oLap.Calories = ByteToShort(PData.ByteData, 36);
                                    oLap.AverageHeartRate = PData.ByteData[38];
                                    oLap.MaxHeartRate = PData.ByteData[39];
                                    oLap.Intensity = PData.ByteData[40];
                                    oLap.AverageCadence = PData.ByteData[41];
                                    oLap.TriggerMethod = PData.ByteData[42];
                                    //bytes 43-45 are unused
                                    oLap.FatCalories = ByteToShort(PData.ByteData, 46);
                                    break;

                                default:
                                    throw new DataTransferException(ERR_MSG_3002);
                            }

                            mcLaps.Add(oLap);
                            //work out percentage done and raise event for the caller
                            //RaiseEvent Progress(Limit((100 / iRecords) * f, , 100))
                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * f), 0, 100)));
                            }
                        }
                        else
                        {
                            throw new DataTransferException(ERR_MSG_3017);
                        }
                    }
                    if (!flags.TransferAbort)
                    {
                        //check for the transfer complete packet
                        PData = ReceiveData();
                        if (PData.Pid != ProcessId.TransferComplete)
                            throw new DataTransferException(ERR_MSG_3017);
                    }
                    else
                    {
                        flags.TransferAbort = false;
                    }
                }
                else
                {
                    if (PData.Pid != ProcessId.TransferComplete)
                        throw new DataTransferException(ERR_MSG_3017);
                }

                //reset the callers progress indicator
                //RaiseEvent Progress(0);
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
                //				}			
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return mcLaps;
        }

        /// <summary>
        /// Accepts a Tracklog object and transfers it to the connected GPS device.
        /// A value of True is returned if the transfer was successful.
        /// </summary>
        /// <example>C#
        /// <code>
        /// public void SetTracklog()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        /// 
        ///     Tracklog trk;
        ///     Trackpoint tpt;
        ///
        ///     //Create a new Tracklog object
        ///     trk = new Tracklog();
        ///     trk.Identifier = "Upload";
        ///
        ///     //Create the first Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.42;
        ///     tpt.Latitude = -36.47;
        ///     tpt.NewTrack = true;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Create a second Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.43;
        ///     tpt.Latitude = -36.48;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Create the third Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.44;
        ///     tpt.Latitude = -36.49;
        ///     tpt.NewTrack = true;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Create the fourth Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.45;
        ///     tpt.Latitude = -36.5;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Send the Tracklog to the GPS device
        ///     gps.SetTrackLog(trk);
        ///
        /// }
        /// </code>
        /// </example>
        public override bool SetTrackLog(Tracklog tracklog)
        {
            return SetTrackLog(tracklog, false);
        }
        /// <summary>
        /// The SplitTracklog parameter, when set to true allows a multi-segment
        /// Tracklog object to be transferred to the GPS as multiple tracklogs.
        /// A setting of false ensures a multi-segment Tracklog object is transferred
        /// to the GPS device as a single track.
        /// </summary>
        /// <example>C#
        /// <code>
        /// public void SetTracklog()
        /// {
        ///     GarminDevice gps = new GarminDevice();
        /// 
        ///     Tracklog trk;
        ///     Trackpoint tpt;
        ///
        ///     //Create a new Tracklog object
        ///     trk = new Tracklog();
        ///     trk.Identifier = "Upload";
        ///
        ///     //Create the first Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.42;
        ///     tpt.Latitude = -36.47;
        ///     tpt.NewTrack = true;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Create a second Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.43;
        ///     tpt.Latitude = -36.48;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Create the third Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.44;
        ///     tpt.Latitude = -36.49;
        ///     tpt.NewTrack = true;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Create the fourth Trackpoint
        ///     tpt = new Trackpoint();
        ///     tpt.Longitude = 146.45;
        ///     tpt.Latitude = -36.5;
        ///     trk.Trackpoints.Add(tpt);
        ///
        ///     //Send the Tracklog to the GPS device
        ///     gps.SetTrackLog(trk);
        ///
        /// }
        /// </code>
        /// </example>
        public override bool SetTrackLog(Tracklog tracklog, bool splitTracklog)
        {
            GpsPidData PData;
            //TrackPoint oTrackPoint = null;
            int iRecords = 0;
            int iTrackPoints = 0;
            int iPackets = 0;
            int intTLCount = 0;
            string strIdentifier;
            string strDataProtocol = "";
            string strProtocol = "";
            TracklogCollection objTracklogs = null;

            //byte[] Packet;
            bool bA301 = false;
            PData.Pid = 0;
            PData.ByteData = null;

            try
            {

                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                if (splitTracklog)
                {
                    //tracklogs are to be split based on the new_track property
                    objTracklogs = SplitTrackLog(tracklog);
                }
                else
                {
                    //tracklog to be treared as single tracklog
                    objTracklogs = new TracklogCollection();
                    objTracklogs.Add(tracklog);
                }

                // get the number of tracklogs to ensure progress reports correctly
                intTLCount = objTracklogs.Count;

                foreach (Tracklog objTracklog in objTracklogs)
                {

                    if (objTracklog != null)
                    {

                        TraceLog.WriteMessage("Sending Tracklog to GPS", TraceLevel.Info, true);

                        strProtocol = mcProtocolTable["Track"].ToString();

                        if (strProtocol == "A301" || strProtocol == "A302")
                            bA301 = true;

                        //calculate packets to send e.g. headers and trackpoints
                        //first the track points
                        iTrackPoints = objTracklog.Trackpoints.Count;

                        //then the header
                        if (bA301)
                        {
                            iRecords = iTrackPoints + 1;
                        }
                        else //A300
                        {
                            iRecords = iTrackPoints;
                        }

                        //reset the link
                        DeviceCommand(CommandID.AbortTransfer);

                        //clear the buffer
                        ClearBuffers();

                        //create the first packet
                        PData.Pid = ProcessId.Records;

                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte((short)iRecords));
                        if (!TransmitData(PData))
                            throw new DataTransferException(ERR_MSG_3007);


                        if (bA301) //send the track header
                        {
                            switch (mcProtocolTable["TrackD0"].ToString())
                            {
                                case "D310":

                                    //set the header stuff here
                                    PData.Pid = ProcessId.TrackHeader;

                                    //display
                                    if (objTracklog.Display)
                                    {
                                        PData.ByteData = ShortToByte(1, true);
                                    }
                                    else
                                    {
                                        PData.ByteData = ShortToByte(0, true);
                                    }

                                    //colour
                                    PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(objTracklog.Colour, true));

                                    //identifier
                                    if (objTracklog.Identifier.Length > 50)
                                        strIdentifier = objTracklog.Identifier.Substring(1, 50);
                                    else
                                        strIdentifier = objTracklog.Identifier;

                                    PData.ByteData = ByteConcat(PData.ByteData, StringToByte(strIdentifier, true));

                                    //send header
                                    if (!TransmitData(PData))
                                        throw new DataTransferException(ERR_MSG_3007);

                                    iPackets = iPackets + 1;
                                    break;

                                case "D311":

                                    //set the header stuff here
                                    PData.Pid = ProcessId.TrackHeader;
                                    PData.ByteData = ShortToByte(objTracklog.Index, false);

                                    //send header
                                    if (!TransmitData(PData))
                                        throw new DataTransferException(ERR_MSG_3007);

                                    iPackets = iPackets + 1;

                                    break;

                                case "D312":

                                    //set the header stuff here
                                    PData.Pid = ProcessId.TrackHeader;

                                    //display
                                    if (objTracklog.Display)
                                    {
                                        PData.ByteData = ShortToByte(1, true);
                                    }
                                    else
                                    {
                                        PData.ByteData = ShortToByte(0, true);
                                    }

                                    //colour
                                    PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(objTracklog.Colour, true));

                                    //identifier
                                    if (objTracklog.Identifier.Length > 50)
                                        strIdentifier = objTracklog.Identifier.Substring(1, 50);
                                    else
                                        strIdentifier = objTracklog.Identifier;

                                    PData.ByteData = ByteConcat(PData.ByteData, StringToByte(strIdentifier, true));

                                    //send header
                                    if (!TransmitData(PData))
                                        throw new DataTransferException(ERR_MSG_3007);

                                    iPackets = iPackets + 1;
                                    break;

                                default:
                                    throw new DataTransferException(ERR_MSG_3002);
                            }
                            //get the data protocol from D1
                            strDataProtocol = mcProtocolTable["TrackD1"].ToString();

                        }
                        else
                        {
                            //no header so data is D0
                            strDataProtocol = mcProtocolTable["TrackD0"].ToString();
                        }

                        //loop through all the trackpoints
                        foreach (Trackpoint oTrackPoint in objTracklog.Trackpoints)
                        {

                            //common to all protocols
                            PData.Pid = ProcessId.TrackData;

                            PData.ByteData = IntToByte(DegreesToSemicircles(oTrackPoint.Latitude));
                            PData.ByteData = ByteConcat(PData.ByteData, IntToByte(DegreesToSemicircles(oTrackPoint.Longitude)));
                            PData.ByteData = ByteConcat(PData.ByteData, IntToByte(oTrackPoint.Time));

                            switch (strDataProtocol)
                            {
                                case "D300":
                                    if (oTrackPoint.NewTrack)
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(1, true));
                                    else
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(0, true));

                                    break;

                                case "D301":
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Altitude));
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Depth));

                                    if (oTrackPoint.NewTrack)
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(1, true));
                                    else
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(0, true));
                                    break;

                                case "D302":
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Altitude));
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Depth));
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Temperature));

                                    if (oTrackPoint.NewTrack)
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(1, true));
                                    else
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(0, true));
                                    break;

                                case "D303":
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Altitude));
                                    PData.ByteData = ByteConcat(PData.ByteData, IntToByte(oTrackPoint.HeartRate, true));
                                    break;

                                case "D304":
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Altitude));
                                    PData.ByteData = ByteConcat(PData.ByteData, SingleToByte(oTrackPoint.Distance));
                                    PData.ByteData = ByteConcat(PData.ByteData, IntToByte(oTrackPoint.HeartRate, true));
                                    PData.ByteData = ByteConcat(PData.ByteData, IntToByte(oTrackPoint.Cadence, true));

                                    if (oTrackPoint.Sensor)
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(1, true));
                                    else
                                        PData.ByteData = ByteConcat(PData.ByteData, ShortToByte(0, true));
                                    break;

                                default:
                                    throw new DataTransferException(ERR_MSG_3002);
                            }

                            if (!TransmitData(PData))
                                throw new DataTransferException(ERR_MSG_3007);

                            //RaiseEvent Progress(Limit(CLng((100 / iRecords) * iPackets), , 100))
                            //RaiseEvent Progress(0)
                            if (Progress != null)
                            {
                                Progress(this, new ProgressEventArgs(Limit((int)((100.0 / iRecords) * iPackets / intTLCount), 0, 100)));
                            }
                            iPackets = iPackets + 1;

                            if (flags.TransferAbort)
                            {
                                DeviceCommand(CommandID.AbortTransfer);
                                TraceLog.WriteMessage("Transfer Aborted by Client", TraceLevel.Info, true);
                                break;
                            }
                        }

                        if (!flags.TransferAbort)
                        {
                            //send transfer complete packet
                            PData.Pid = ProcessId.TransferComplete;
                            PData.ByteData = ShortToByte((short)CommandID.TransferTrk);
                            if (!TransmitData(PData))
                                throw new DataTransferException(ERR_MSG_3007);
                        }
                        else
                        {
                            //reset the cancel flag
                            flags.TransferAbort = false;
                        }

                        //					if (Progress != null)
                        //					{
                        //						Progress(this, new ProgressEventArgs(0));
                        //					}

                    }
                    else
                    {
                        //tracklog was null so return false
                        return false;
                    }
                    //reduce this as each Tracklog is sent (used for progress calc)
                    intTLCount--;
                }
                if (Progress != null)
                {
                    Progress(this, new ProgressEventArgs(0));
                }
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            //if we get here then all is well
            return true;
        }
        /// <summary>
        /// This method returns the Unit ID of the connected device. Please note that
        /// this method is not supported by all devices. A DataTransferException 
        /// exception will be thrown if the method is not supported.
        /// </summary>
        /// <returns></returns>
        public long GetUnitId()
        {
            GpsPidData PData; //create user variable to pass to the link layertransmit function
            long result = 0;

            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                PortOpen();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                // get the usin ID (undocumented)
                DeviceCommand(CommandID.TransferUnitId);

                //check for the returned data
                PData = ReceiveData();

                result = (long)ByteToUInt(PData.ByteData, 0);
            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }

            return result;
        }
        /// <summary>
        /// This method returns a Product info object containing information about the connected device.
        /// </summary>
        /// <returns>ProductInfo object</returns>
        public override ProductInfo GetProductInfo()
        {
            //---------------------------------------------------------------------------
            //   DESCRIPTION:    This procedure populates the product info properties
            //                   and populates the module level Collection with protocol
            //                   capabilities data.
            //
            //
            //   PARAMETERS:
            //                   <none>
            //
            //   RETURNS:
            //
            //---------------------------------------------------------------------------

            int intByteLength = 0;

            GpsPidData PData; //create user variable to pass to the link layertransmit function
            string strTemp = "";
            int intPosition = 0;

            PData.Pid = 0;
            PData.ByteData = null;

            try
            {
                PortOpen();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //populate the user defined type, dont need to add any data
                PData.Pid = ProcessId.ProductRequest;

                //transmit the packet passing the user defined type
                TransmitData(PData);

                //check for the returned data
                PData = ReceiveData();

                //check whether data has been returned
                if (PData.ByteData == null)
                    intByteLength = 0;
                else
                    intByteLength = PData.ByteData.Length;


                if (intByteLength > 0)
                {

                    //need to check whether this is product info or protocol array
                    if (PData.Pid == ProcessId.ProductData)
                    {

                        //transfer data from the GpsPidData type to the properties
                        mobjProdInfo.ProductId = ByteToShort(PData.ByteData, 0);
                        mobjProdInfo.SoftwareVersion = ByteToShort(PData.ByteData, 2).ToString(CultureInfo.InvariantCulture);

                        //clear the existing description
                        mobjProdInfo.ProductDescription = "";
                        mobjProdInfo.ProductDescription = ByteToString(PData.ByteData, 4, PData.ByteData.GetUpperBound(0) - 3, true, true);

                        //sort out the product name
                        strTemp = mobjProdInfo.ProductDescription;
                        intPosition = strTemp.IndexOf(" Software");
                        if (intPosition == 0)
                            intPosition = strTemp.IndexOf(" SOFTWARE");
                        if (intPosition > 0)
                            strTemp = strTemp.Substring(0, intPosition);

                        mobjProdInfo.ProductName = strTemp;

                        //Debug.WriteLine(mobjProdInfo.ProductName);
                        //Debug.WriteLine(mobjProdInfo.ProductDescription);

                        flags.Connected = true; //this is not used at present
                    }
                    else
                    {
                        // error?
                    }
                }
                else
                {
                    flags.Connected = false; //this is not used at present
                }


                //dont bother with the protocol stuff if we couldn't get a product id
                if (flags.Connected)
                {

                    //loop through packets until the protocol data packets are found (Pid=253)
                    double dblStart = Timer();
                    bool blnTimeout = false;

                    while (!blnTimeout)
                    {
                        //check for the protocol data this is sent following the product data
                        //however some devices return other stuff before the protocol array
                        PData = ReceiveData();

                        if (PData.Pid == ProcessId.ProtocolArray)
                            break;

                        if (Timer() > (dblStart + PROTOCOL_TIMEOUT))
                            blnTimeout = true;
                    }

                    //check whether data has been returned
                    if (PData.Pid == ProcessId.ProtocolArray)
                    {
                        mcProtocolTable = CreateProtocolCollection(PData.ByteData);
                        mobjProdInfo.ProtocolCapability = true;
                    }
                    else
                    {
                        mcProtocolTable = CreateProtocolCollectionFromProductInfo(mobjProdInfo.ProductId, mobjProdInfo.GetSoftwareVersionAsInteger());
                        mobjProdInfo.ProtocolCapability = false;
                    }

                    if (mcProtocolTable.Count > 0)
                    {
                        //now reset the link protocol accordingly, previous communication
                        //was done using L001
                        switch (mcProtocolTable["Link"].ToString())
                        {
                            case "L000":
                                SetTransportProtocol(LinkProtocol.L000);
                                break;
                            case "L001":
                                SetTransportProtocol(LinkProtocol.L001);
                                break;
                            case "L002":
                                SetTransportProtocol(LinkProtocol.L002);
                                break;
                        }

                        //now set the command protocol from the default of A010
                        switch (mcProtocolTable["Command"].ToString())
                        {
                            case "A010":
                                SetDCValues(DCProtocol.A010);
                                break;
                            case "A011":
                                SetDCValues(DCProtocol.A011);
                                break;
                        }

                        //populate the SymbolSet and SymbolMaxValue properties
                        mobjProdInfo.SymbolSet = -1;
                        if (mcProtocolTable["WaypointD0"] != null)
                        {
                            switch (mcProtocolTable["WaypointD0"].ToString())
                            {
                                //8 bit symbol set 0
                                case "D103":
                                case "D107":
                                    mobjProdInfo.SymbolSet = 0;
                                    break;

                                //8 bit symbol set 1
                                case "D101":
                                    mobjProdInfo.SymbolSet = 1;
                                    break;

                                //16 bit symbol set 2
                                case "D102":
                                case "D104":
                                case "D105":
                                case "D106":
                                case "D108":
                                case "D109":
                                case "D110":
                                case "D154":
                                case "D155":
                                    mobjProdInfo.SymbolSet = 2;
                                    //mobjProdInfo.SymbolMaxValue = 65535;
                                    break;

                            }
                        }

                        //log the product data even if if empty
                        TraceLog.WriteMessage(string.Concat("Product ID:           ", mobjProdInfo.ProductId.ToString(CultureInfo.InvariantCulture)), TraceLevel.Info, false, true);
                        TraceLog.WriteMessage(string.Concat("Software Version:     ", mobjProdInfo.SoftwareVersion.ToString(CultureInfo.InvariantCulture)), TraceLevel.Info);
                        TraceLog.WriteMessage(string.Concat("Description:          ", mobjProdInfo.ProductDescription), TraceLevel.Info, false, false, true);

                    }
                    else
                    {
                        //set to false so that the caller can set the protocols manually
                        throw new DataTransferException(ERR_MSG_3008);
                    }
                }
                else
                {
                    //set to false so that the caller can set the protocols manually
                    throw new DataTransferException(ERR_MSG_3015);
                }

            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                throw new DataTransferException(ex.Message, ex);
            }
            finally
            {
                PortClose();
            }
            return mobjProdInfo;
        }

        #endregion

        #region Private Functions

        private void PopulateTrackHeader(Tracklog track, GpsPidData PData)
        {
            int byteCount;

            if (PData.Pid == ProcessId.TrackHeader)
            {
                string trackHeaderProtocol = mcProtocolTable["TrackD0"].ToString();

                //NOTE: this switch is repeated below
                switch (trackHeaderProtocol)
                {
                    case "D310":

                        //get the length of packet
                        byteCount = PData.ByteData.Length;

                        //copy the header info into the Track Log object properties
                        //only one data type for header D310
                        if (PData.ByteData[0] == 0) //zero=false, non-zero=true
                            track.Display = false;
                        else
                            track.Display = true;

                        track.Colour = PData.ByteData[1];
                        track.Identifier = ByteToString(PData.ByteData, 2, byteCount - 2);

                        break;

                    case "D311":

                        //get the length of packet
                        byteCount = PData.ByteData.Length;

                        track.Index = ByteToShort(PData.ByteData, 0);

                        break;

                    case "D312":

                        //get the length of packet
                        byteCount = PData.ByteData.Length;

                        //copy the header info into the Track Log object properties
                        //only one data type for header D310
                        if (PData.ByteData[0] == 0) //zero=false, non-zero=true
                            track.Display = false;
                        else
                            track.Display = true;

                        track.Colour = PData.ByteData[1];
                        track.Identifier = ByteToString(PData.ByteData, 2, byteCount - 2);

                        break;
                }
            }
        }
        private string CleanText(string sData)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function limits the character string to the GPS
            //               character sets. 
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------
            //
            //    'UCase letters 65-90
            //    'LCase letters 97-122
            //    'numbers 48 - 57
            //    'hyphen 45
            //    'space 32
            int iChar = 0;
            string sOut = "";
            try
            {

                if (sData.Length > 0)
                {
                    //upper/lower case letters numbers spaces and hyphen
                    //sData = sData.ToUpper(CultureInfo.InvariantCulture);
                    for (int f = 1; f <= sData.Length; f++)
                    {
                        iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                        if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
                            //sOut = sOut & Chr(iChar);																		  
                            sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return sOut;

        }

        //		private string CharacterSetTrimForShape(string sData)
        //		{
        //			//-------------------------------------------------------------------------
        //			// ABSTRACT:     This function limits the character string and is used
        //			//               when creating shape files.
        //			//
        //			// PARAMETERS:
        //			//
        //			// RETURNS:
        //			//
        //			// DESCRIPTION:
        //			//
        //			//-------------------------------------------------------------------------
        //			//
        //			//    'UCase letters 65-90
        //			//    'LCase letters 97-122
        //			//    'numbers 48 - 57
        //			//    'hyphen 45
        //			//    'space 32
        //
        //			int iChar = 0;
        //			string sOut = "";
        //
        //			try
        //			{
        //				if(sData == null)
        //					sData = "";
        //
        //				if(sData.Length > 0)
        //				{
        //					//upper/lower case letters numbers spaces and hyphen
        //					for(int f = 1; f <= sData.Length; f++)
        //					{
        //						iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
        //						if((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
        //							//sOut = sOut & Chr(iChar);																		  
        //							sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));																		  
        //					}
        //				}			
        //					
        //			}
        //			catch(Exception e)
        //			{
        //				throw e;
        //			}
        //			return sOut;
        //		}
        private string CharacterSetTrim(string sData, GarminDevice.CharSet CharacterSet)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function limits the character string to the GPS
            //               character sets. It is applied only when CharacterSetRules
            //				are enabled.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------
            //
            //    'UCase letters 65-90
            //    'LCase letters 97-122
            //    'numbers 48 - 57
            //    'hyphen 45
            //    'space 32
            int iChar = 0;
            string sOut = "";

            try
            {
                if (sData == null)
                    sData = "";

                // need to see if the charset rules are enabled (enabled by default)
                if (mbCharsetRules)
                {

                    switch (CharacterSet)
                    {
                        case CharSet.UserWaypointIdentifier: // = 1

                            if (sData.Length > 0)
                            {
                                //upper case letters and numbers
                                sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57))
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }

                            break;

                        case CharSet.WaypointComment: // = 2

                            if (sData.Length > 0)
                            {
                                //upper case letters numbers spaces and hyphen
                                sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }
                            break;

                        case CharSet.RouteComment: // = 3

                            if (sData.Length > 0)
                            {
                                //upper case letters numbers spaces and hyphen
                                sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }
                            break;
                        case CharSet.City: // = 4
                            //any ascii char, ignored by GPS anyway
                            sOut = sData;
                            break;

                        case CharSet.State: // = 5
                            //any ascii char, ignored by GPS anyway
                            sOut = sData;
                            break;

                        case CharSet.FacilityName: // = 6
                            //any ascii char, ignored by GPS anyway
                            sOut = sData;
                            break;

                        case CharSet.CountryCode: // = 7

                            if (sData.Length > 0)
                            {
                                //upper case letters numbers and spaces
                                sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32)
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }
                            break;

                        case CharSet.RouteIdentifier: // = 8

                            if (sData.Length > 0)
                            {
                                //upper case letters numbers spaces and hyphen
                                sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }
                            break;

                        case CharSet.RouteWaypointIdentifier: // = 9
                            //any ascii char
                            sOut = sData;
                            break;
                        case CharSet.LinkIdentifier: // = 10
                            //any ascii char
                            sOut = sData;
                            break;
                        case CharSet.TrackIdentifier: // = 11

                            if (sData.Length > 0)
                            {
                                //upper case letters numbers spaces and hyphen
                                sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
                                        //sOut = sOut & Chr(iChar);																		  
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }
                            break;
                        case CharSet.ShapeFile: // = 12

                            if (sData.Length > 0)
                            {
                                //upper/lower case letters numbers spaces and hyphen
                                //sData = sData.ToUpper(CultureInfo.InvariantCulture);
                                for (int f = 1; f <= sData.Length; f++)
                                {
                                    iChar = Microsoft.VisualBasic.Strings.Asc(sData.Substring(f - 1, 1));
                                    if ((iChar >= 65 && iChar <= 90) || (iChar >= 48 && iChar <= 57) || iChar == 32 || iChar == 45)
                                        //sOut = sOut & Chr(iChar);																		  
                                        sOut = string.Concat(sOut, Microsoft.VisualBasic.Strings.Chr(iChar));
                                }
                            }
                            break;

                        default:
                            break;
                    }
                }
                else
                {
                    sOut = sData;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return sOut;
        }

        private Trackpoint CreateTrackPointObject(byte[] Packet)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function creates a single Track Point object based
            //               on the packet passed.
            //
            // PARAMETERS:   Data as a Byte Array
            //
            // RETURNS:      TrackPoint object
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            Trackpoint oTrackPoint = null;
            int lData = 0;
            string sProtocol = "";

            //TEST populate from text
            //Packet = PopulatePacketFromString("00 7F F6 21 00 F3 06 A9 FB 24 48 1B 20 6A 7C 42 51 59 04 69 00 44 2D 01");

            try
            {
                if (mcProtocolTable["Track"].ToString() == "A301" || mcProtocolTable["Track"].ToString() == "A302")
                {
                    //headers are used therefor data defined by D1
                    sProtocol = "TrackD1";
                }
                else
                {
                    //headers not used data defined by D0
                    sProtocol = "TrackD0";
                }

                oTrackPoint = new Trackpoint();

                //could be d1 if trk headers are used
                switch (mcProtocolTable[sProtocol].ToString())
                {
                    case "D300":

                        //get the lattitude
                        lData = ByteToInt(Packet, 0);
                        oTrackPoint.Latitude = SemicircleToDegrees(lData);

                        //get the longitude
                        lData = ByteToInt(Packet, 4);
                        oTrackPoint.Longitude = SemicircleToDegrees(lData);

                        //time
                        oTrackPoint.Time = ByteToInt(Packet, 8);

                        if (Packet[12] == 0)
                        {
                            //zero=false, non-zero=true
                            oTrackPoint.NewTrack = false;
                        }
                        else
                        {
                            oTrackPoint.NewTrack = true;
                        }
                        break;

                    case "D301":

                        //get the lattitude
                        lData = ByteToInt(Packet, 0);
                        oTrackPoint.Latitude = SemicircleToDegrees(lData);

                        //get the longitude
                        lData = ByteToInt(Packet, 4);
                        oTrackPoint.Longitude = SemicircleToDegrees(lData);

                        oTrackPoint.Time = ByteToInt(Packet, 8);
                        oTrackPoint.Altitude = ByteToSingle(Packet, 12);
                        oTrackPoint.Depth = ByteToSingle(Packet, 16);

                        if (Packet[20] == 0)
                        {
                            //zero=false, non-zero=true
                            oTrackPoint.NewTrack = false;
                        }
                        else
                        {
                            oTrackPoint.NewTrack = true;
                        }
                        break;

                    case "D302":

                        //get the lattitude
                        lData = ByteToInt(Packet, 0);
                        oTrackPoint.Latitude = SemicircleToDegrees(lData);

                        //get the longitude
                        lData = ByteToInt(Packet, 4);
                        oTrackPoint.Longitude = SemicircleToDegrees(lData);

                        oTrackPoint.Time = ByteToInt(Packet, 8);
                        oTrackPoint.Altitude = ByteToSingle(Packet, 12);
                        oTrackPoint.Depth = ByteToSingle(Packet, 16);
                        oTrackPoint.Temperature = ByteToSingle(Packet, 20);

                        if (Packet[24] == 0)
                        {
                            //zero=false, non-zero=true
                            oTrackPoint.NewTrack = false;
                        }
                        else
                        {
                            oTrackPoint.NewTrack = true;
                        }
                        break;

                    case "D303":

                        //get the lattitude
                        lData = ByteToInt(Packet, 0);
                        oTrackPoint.Latitude = SemicircleToDegrees(lData);

                        //get the longitude
                        lData = ByteToInt(Packet, 4);
                        oTrackPoint.Longitude = SemicircleToDegrees(lData);

                        oTrackPoint.Time = ByteToInt(Packet, 8);
                        oTrackPoint.Altitude = ByteToSingle(Packet, 12);
                        oTrackPoint.HeartRate = Packet[16];

                        break;

                    case "D304":

                        //get the lattitude
                        lData = ByteToInt(Packet, 0);
                        oTrackPoint.Latitude = SemicircleToDegrees(lData);

                        //get the longitude
                        lData = ByteToInt(Packet, 4);
                        oTrackPoint.Longitude = SemicircleToDegrees(lData);

                        oTrackPoint.Time = ByteToInt(Packet, 8);
                        oTrackPoint.Altitude = ByteToSingle(Packet, 12);
                        oTrackPoint.Distance = ByteToSingle(Packet, 16);
                        oTrackPoint.HeartRate = Packet[20];
                        oTrackPoint.Cadence = Packet[21];

                        if (Packet[22] == 0)
                            //zero=false, non-zero=true
                            oTrackPoint.Sensor = false;
                        else
                            oTrackPoint.Sensor = true;

                        break;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return oTrackPoint;
        }

        private Position GetLatLong()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     Gets the current position from the gps and populates the
            //               properties
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            GpsPidData PData;
            double dblTemp = 0;
            Position objPos = new Position();

            PData.Pid = 0;
            PData.ByteData = null;


            try
            {
                if (mobjProdInfo.ProductId == 0)
                    GetProductInfo();

                PortOpen();

                //reset the link
                DeviceCommand(CommandID.AbortTransfer);

                //clear the buffer
                ClearBuffers();

                //get the posistion data
                DeviceCommand(CommandID.TransferPos);

                PData = ReceiveData();

                if (PData.Pid == ProcessId.PositionData)
                {
                    //latitude (double)
                    dblTemp = ByteToDouble(PData.ByteData, 0);
                    objPos.Latitude = RadiansToDegrees(dblTemp) + mdblRandomError;

                    //longitude (double)
                    dblTemp = ByteToDouble(PData.ByteData, 8);
                    objPos.Longitude = RadiansToDegrees(dblTemp) + mdblRandomError;
                }
                else
                {
                    throw new DataTransferException(ERR_MSG_3003);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                PortClose();
            }
            return objPos;
        }

        private void PvtDataThread()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function returns the Position Velocity Time (PVT)
            //               data as a string and is called from a separate thread.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            //the string delimiter
            const string DELIM = ";";

            GpsPidData PData;
            float sglTemp = 0.0f;
            short iTemp = 0;
            double dblTemp = 0.0;
            int lTemp = 0;
            string sPVTData = "";
            PData.Pid = 0;
            PData.ByteData = null;
            StringBuilder sbSentence = null;
            bool pvtDataRunning;

            //do not reset the link (abort transfer) or clear the buffer as this is called from
            //an event in real time

            try
            {
                PortOpen();

                //if this is set to true no events will be fired by GetPVTData
                //DeviceCommand(CommandID.ReceiverMeasurementOn);
                DeviceCommand(CommandID.StartPVTData);

                flags.PvtRunning = true;

                while (!flags.TransferAbort)
                {

                    //get data
                    Debug.WriteLine("Getting PVT Data");

                    PData = ReceiveData();
                    Debug.WriteLine("ReceiveData Completed");

                    //could be PVT, satellite or receiver measurement
                    switch (PData.Pid)
                    {
                        case ProcessId.PvtData:

                            //extract data into a string
                            sbSentence = new StringBuilder();
                            sbSentence.Append("Type=PVTData;");

                            sbSentence.Append("Altitude=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 0).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("EPE=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 4).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("EPH=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 8).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("EPV=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 12).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("Fix=");
                            sbSentence.Append(ByteToShort(PData.ByteData, 16).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("TOW=");
                            sbSentence.Append(ByteToDouble(PData.ByteData, 18).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            dblTemp = ByteToDouble(PData.ByteData, 26) + mdblRandomError;
                            sbSentence.Append("Latitude=");
                            sbSentence.Append(RadiansToDegrees(dblTemp).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            dblTemp = ByteToDouble(PData.ByteData, 34) + mdblRandomError;
                            sbSentence.Append("Longitude=");
                            sbSentence.Append(RadiansToDegrees(dblTemp).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("East=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 42).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("North=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 46).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("Up=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 50).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("MSLHeight=");
                            sbSentence.Append(ByteToSingle(PData.ByteData, 54).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sbSentence.Append("WNDays=");
                            sbSentence.Append(ByteToInt(PData.ByteData, 58).ToString(CultureInfo.InvariantCulture));
                            sbSentence.Append(DELIM);

                            sPVTData = sbSentence.ToString();

                            break;

                        case ProcessId.ReceiverMeasurement:
                            sPVTData = "Type=ReceiverMeasurement";
                            break;

                        case ProcessId.SatelliteData:

                            sbSentence = new StringBuilder();
                            sPVTData = "Type=Satellite;";
                            sPVTData = sPVTData + GetSatellite(PData.ByteData);
                            sPVTData = sPVTData + "\r\n";

                            break;

                        default:
                            sPVTData = "None";
                            break;
                    }

                    //System.Windows.Forms.Application.DoEvents();
                    //Thread.Sleep(100);

                    //raise the event
                    if (PvtData != null)
                    {
                        Debug.WriteLine(sPVTData);
                        PvtData(this, new PvtDataEventArgs(sPVTData));
                    }

                }

            }
            catch (Exception ex)
            {
                TraceLog.WriteError(ex);
                //throw ex;
            }
            finally
            {
                //loop ended so reset abort flag
                if (flags.PvtRunning)
                {
                    DeviceCommand(CommandID.StopPVTData);
                    //DeviceCommand(CommandID.ReceiverMeasurementOff);

                    flags.PvtRunning = false;

                }
                if (flags.TransferAbort)
                {
                    flags.TransferAbort = false;
                }
                PortClose();
            }
        }
        private void ReceiverMeasurementDataThread()
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function returns the Receiver Measurement
            //               data as a string.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            //the string delimiter

            GpsPidData PData;
            string sPVTData = "";
            PData.Pid = 0;
            PData.ByteData = null;
            bool pvtDataRunning;


            //do not reset the link (abort transfer) or clear the buffer as this is called from
            //an event in real time

            try
            {
                PortOpen();

                //if this is set to true no events will be fired by GetPVTData			
                DeviceCommand(CommandID.ReceiverMeasurementOn);

                flags.PvtRunning = true;

                while (!flags.TransferAbort)
                {

                    //get data
                    PData = ReceiveData();

                    //could be PVT, satellite or receiver measurement
                    switch (PData.Pid)
                    {
                        case ProcessId.ReceiverMeasurement:
                            sPVTData = "Type=ReceiverMeasurement;";
                            break;

                        case ProcessId.SatelliteData:

                            StringBuilder sbSentence = new StringBuilder();

                            sPVTData = "Type=Satellite;";
                            sPVTData = sPVTData + GetSatellite(PData.ByteData);

                            break;

                        default:
                            sPVTData = "";
                            break;
                    }

                    System.Windows.Forms.Application.DoEvents();

                    //raise the event
                    if (PvtData != null)
                    {
                        PvtData(this, new PvtDataEventArgs(sPVTData));
                    }

                    System.Windows.Forms.Application.DoEvents();

                }
                
                //loop ended so reset abort flag
                if (flags.PvtRunning)
                {
                    DeviceCommand(CommandID.ReceiverMeasurementOff);

                    flags.PvtRunning = false;
                }

                if (flags.TransferAbort)
                {
                    flags.TransferAbort = false;
                }
            }
            catch (Exception ex)
            {
                DeviceCommand(CommandID.StopPVTData);
                flags.PvtRunning = false;

                TraceLog.WriteError(ex);
                throw ex;
            }
            finally
            {
                PortClose();
            }
        }
        private string GetSatellite(byte[] data)
        {
            const int BYTES_PER_SATELLITE = 7;
            const int NUMBER_OF_SATELLITES = 12;
            const string SENTENCE_DELIMITER = ";";
            int byteOffset = 0;

            StringBuilder sbSentence = new StringBuilder();

            try
            {
                for (int satNumber = 0; satNumber <= NUMBER_OF_SATELLITES - 1; satNumber++)
                {
                    byteOffset = satNumber * BYTES_PER_SATELLITE;

                    sbSentence.Append("Svid=");
                    sbSentence.Append(data[0 + byteOffset].ToString(CultureInfo.InvariantCulture));  //Satellite Number svid
                    sbSentence.Append(SENTENCE_DELIMITER);

                    sbSentence.Append("SigToNoise=");
                    sbSentence.Append(ByteToShort(data, 1 + byteOffset).ToString(CultureInfo.InvariantCulture));//sig to noise
                    sbSentence.Append(SENTENCE_DELIMITER);

                    sbSentence.Append("Elevation=");
                    sbSentence.Append(data[3 + byteOffset].ToString(CultureInfo.InvariantCulture));//elevation
                    sbSentence.Append(SENTENCE_DELIMITER);

                    sbSentence.Append("Azimuth=");
                    sbSentence.Append(ByteToShort(data, 4 + byteOffset).ToString(CultureInfo.InvariantCulture));//azimuth
                    sbSentence.Append(SENTENCE_DELIMITER);

                    sbSentence.Append("Status=");
                    sbSentence.Append(((byte)7 & data[6 + byteOffset]).ToString(CultureInfo.InvariantCulture));//status

                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return sbSentence.ToString();

        }

        private void ClearBuffers()
        {
            if (mbUSBTransport)
                mUSB.ClearBuffer();
            else
                mLink.ClearBuffer();
        }
        private bool TransmitData(GpsPidData PData)
        {
            return TransmitData(PData, true);
        }

        private bool TransmitData(GpsPidData PData, bool bACKRequired)
        {

            if (mbUSBTransport)
                return mUSB.TransmitData(PData);
            else
                return mLink.TransmitData(PData, bACKRequired);

        }
        private GpsPidData ReceiveData()
        {
            if (mbUSBTransport)
                return mUSB.ReceiveData();
            else
                return mLink.ReceiveData();

        }

        private Waypoint CreateWaypointObject(byte[] Packet)
        {
            return CreateWaypointObject(Packet, WaypointType.Waypoint);
        }
        private Waypoint CreateWaypointObject(byte[] Packet, WaypointType WaypointType)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function populates the waypoint object from the
            //               data bytes passed.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            //byte[] bytData = null; //this is used as temp storage to transfer the data
            int iData = 0;
            //int lData = 0;
            string sText = "";
            string sDataType = "";
            Waypoint oWaypoint = null;
            int lByteCount = 0;

            try
            {
                oWaypoint = new Waypoint();

                //get the length of packet
                lByteCount = Packet.Length;

                //determine data type to use
                switch (WaypointType)
                {
                    case WaypointType.Waypoint:
                        sDataType = mcProtocolTable["WaypointD0"].ToString();
                        break;
                    case WaypointType.Proximity:
                        sDataType = mcProtocolTable["ProximityD0"].ToString();
                        break;
                    case WaypointType.Route:
                        sDataType = mcProtocolTable["RouteD1"].ToString();
                        break;
                }

                //do the common properties, however these dont apply to D105, D106, D108 and D150
                if (sDataType != "D105" &&
                    sDataType != "D106" &&
                    sDataType != "D108" &&
                    sDataType != "D109" &&
                    sDataType != "D110" &&
                    sDataType != "D150")
                {
                    oWaypoint.Identifier = ByteToString(Packet, 0, 6, false, true);
                    oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 6));
                    oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 10));
                    oWaypoint.Unused1 = ByteToInt(Packet, 14);
                    oWaypoint.Comment = ByteToString(Packet, 18, 40, false, true);
                }

                //the same process is used as that above so no comments added
                switch (sDataType)
                {
                    case "D100":
                    case "D400":
                        if (sDataType == "D400")
                            oWaypoint.ProximityDistance = ByteToSingle(Packet, 58); //Prox Wpt
                        break;
                    case "D101":
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 58);
                        oWaypoint.Symbol = Packet[62];
                        break;
                    case "D102":
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 58);
                        oWaypoint.Symbol = ByteToShort(Packet, 62);
                        break;
                    case "D103":
                    case "D403":
                        oWaypoint.Symbol = Packet[58];
                        oWaypoint.Display = Packet[59];
                        if (sDataType == "D403")
                            oWaypoint.ProximityDistance = ByteToSingle(Packet, 60);
                        break;
                    case "D104":
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 58);
                        oWaypoint.Symbol = ByteToShort(Packet, 62);
                        oWaypoint.Display = Packet[64];
                        break;
                    case "D105":
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 0));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 4));
                        oWaypoint.Symbol = ByteToShort(Packet, 8);
                        oWaypoint.Identifier = ByteToString(Packet, 10, lByteCount - 10, false, true);
                        break;
                    case "D106":
                        oWaypoint.Class = Packet[0];
                        //the next part is a byte array not a string but we store it as a string
                        //but not a standard vb UniCode string
                        oWaypoint.Subclass = new Subclass(CopyByteArray(Packet, 1, 13));  //005
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 14));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 18));
                        oWaypoint.Symbol = ByteToShort(Packet, 22);

                        sText = ByteToString(Packet, 24, lByteCount - 24, false, false);
                        iData = sText.IndexOf("\0");

                        if (iData > 0)
                        {
                            oWaypoint.Identifier = sText.Substring(0, iData); // same as Left(sText , iData - 1)
                            oWaypoint.Link = sText.Substring(iData + 1); // same as Mid(sText, iData - 1)
                        }
                        break;
                    case "D107":
                        oWaypoint.Symbol = Packet[58];
                        oWaypoint.Display = Packet[59];
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 60);
                        oWaypoint.Colour = Packet[64];
                        break;
                    case "D108":
                        oWaypoint.Class = Packet[0];
                        oWaypoint.Colour = Packet[1];
                        oWaypoint.Display = Packet[2];
                        oWaypoint.Attributes = Packet[3];
                        oWaypoint.Symbol = ByteToShort(Packet, 4);
                        oWaypoint.Subclass = new Subclass(CopyByteArray(Packet, 6, 18));
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 24));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 28));
                        oWaypoint.Altitude = ByteToSingle(Packet, 32);
                        oWaypoint.Depth = ByteToSingle(Packet, 36);
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 40);
                        oWaypoint.State = ByteToString(Packet, 44, 2, true, true);
                        oWaypoint.Country = ByteToString(Packet, 46, 2, true, true);

                        sText = ByteToString(Packet, 48, lByteCount - 48, false, false);

                        //split the string into its component parts, could use the split command here
                        //but the subscript errors it can produce make it a waste of time
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Identifier = sText.Substring(0, iData);
                        else if (sText.Length > 0)
                            oWaypoint.Identifier = sText;

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Comment = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Facility = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.City = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Address = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Crossroad = sText.Substring(0, iData);
                        break;
                    case "D109":
                        oWaypoint.PacketType = Packet[0];
                        oWaypoint.Class = Packet[1];
                        oWaypoint.Colour = Packet[2];
                        oWaypoint.Attributes = Packet[3];
                        oWaypoint.Symbol = ByteToShort(Packet, 4);
                        oWaypoint.Subclass = new Subclass(CopyByteArray(Packet, 6, 18));
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 24));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 28));
                        oWaypoint.Altitude = ByteToSingle(Packet, 32);
                        oWaypoint.Depth = ByteToSingle(Packet, 36);
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 40);
                        oWaypoint.State = ByteToString(Packet, 44, 2, true, true);
                        oWaypoint.Country = ByteToString(Packet, 46, 2, true, true);
                        oWaypoint.TimeENRoute = ByteToInt(Packet, 48);

                        sText = ByteToString(Packet, 52, lByteCount - 52, false, false);

                        //split the string into its component parts, could use the split command here
                        //but the subscript errors it can produce make it a waste of time
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Identifier = sText.Substring(0, iData);
                        else if (sText.Length > 0)
                            oWaypoint.Identifier = sText;

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Comment = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Facility = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.City = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Address = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Crossroad = sText.Substring(0, iData);
                        break;
                    case "D110":
                        oWaypoint.PacketType = Packet[0];
                        oWaypoint.Class = Packet[1];
                        oWaypoint.Colour = Packet[2];
                        oWaypoint.Attributes = Packet[3];
                        oWaypoint.Symbol = ByteToShort(Packet, 4);
                        oWaypoint.Subclass = new Subclass(CopyByteArray(Packet, 6, 18));
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 24));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 28));
                        oWaypoint.Altitude = ByteToSingle(Packet, 32);
                        oWaypoint.Depth = ByteToSingle(Packet, 36);
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 40);
                        oWaypoint.State = ByteToString(Packet, 44, 2, true, true);
                        oWaypoint.Country = ByteToString(Packet, 46, 2, true, true);
                        oWaypoint.TimeENRoute = ByteToInt(Packet, 48);
                        oWaypoint.Temperature = ByteToSingle(Packet, 52);
                        oWaypoint.Timestamp = ByteToInt(Packet, 56);
                        oWaypoint.Timestamp = ByteToShort(Packet, 60);
                        sText = ByteToString(Packet, 62, lByteCount - 62, false, false);

                        //split the string into its component parts, could use the split command here
                        //but the subscript errors it can produce make it a waste of time
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Identifier = sText.Substring(0, iData);
                        else if (sText.Length > 0)
                            oWaypoint.Identifier = sText;

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Comment = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Facility = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.City = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Address = sText.Substring(0, iData);

                        sText = sText.Substring(iData + 1);
                        iData = sText.IndexOf("\0");
                        if (iData > 0)
                            oWaypoint.Crossroad = sText.Substring(0, iData);
                        break;
                    case "D150":
                        oWaypoint.Identifier = ByteToString(Packet, 0, 6);
                        oWaypoint.Country = ByteToString(Packet, 6, 2);
                        oWaypoint.Class = Packet[8];
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 9));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 13));
                        oWaypoint.Altitude = ByteToShort(Packet, 17);
                        oWaypoint.City = ByteToString(Packet, 19, 24);
                        oWaypoint.State = ByteToString(Packet, 43, 2);
                        oWaypoint.Facility = ByteToString(Packet, 45, 30);
                        oWaypoint.Comment = ByteToString(Packet, 75, 40);
                        break;
                    case "D151":
                    case "D152":
                    case "D154":
                    case "D155":
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 58);
                        oWaypoint.Facility = ByteToString(Packet, 62, 30);
                        oWaypoint.City = ByteToString(Packet, 92, 24);
                        oWaypoint.State = ByteToString(Packet, 116, 2);
                        oWaypoint.Altitude = ByteToShort(Packet, 118);
                        oWaypoint.Country = ByteToString(Packet, 120, 2);
                        oWaypoint.Unused2 = Packet[122];
                        oWaypoint.Class = Packet[123];

                        if (sDataType == "D154")
                            oWaypoint.Symbol = ByteToShort(Packet, 124);

                        if (sDataType == "D155")
                        {
                            oWaypoint.Symbol = ByteToShort(Packet, 124);
                            oWaypoint.Display = Packet[126];
                        }
                        break;
                    case "D450":
                        oWaypoint.ProximityIndex = ByteToShort(Packet, 0);
                        oWaypoint.Identifier = ByteToString(Packet, 2, 6);
                        oWaypoint.Country = ByteToString(Packet, 8, 2);
                        oWaypoint.Class = Packet[10];
                        oWaypoint.Latitude = SemicircleToDegrees(ByteToInt(Packet, 11));
                        oWaypoint.Longitude = SemicircleToDegrees(ByteToInt(Packet, 15));
                        oWaypoint.Altitude = ByteToShort(Packet, 19);
                        oWaypoint.City = ByteToString(Packet, 21, 24);
                        oWaypoint.State = ByteToString(Packet, 45, 2);
                        oWaypoint.Facility = ByteToString(Packet, 47, 30);
                        oWaypoint.Comment = ByteToString(Packet, 77, 40);
                        oWaypoint.ProximityDistance = ByteToSingle(Packet, 117);
                        break;
                    default:

                        throw new CreateWaypointException(ERR_MSG_3002);
                }
                //determine symbol id
                oWaypoint.SymbolIdentifier = GetSymbolId(oWaypoint.Symbol);
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return oWaypoint;
        }

        //		public Port CommunicationsPort
        //		{
        //			set
        //			{
        //				int intPort = 0;
        //
        //				//store incoming port number
        //				intPort = (int)value;
        //
        //				if(intPort == 0)
        //				{
        //					mbUSBTransport = true;
        //				}
        //				else
        //				{
        //					mbUSBTransport = false;
        //
        //					//close any previously opened comm port
        //					mLink.PortClose();
        //
        //					//re-open the specified port
        //					mLink.PortOpen(intPort);
        //				}
        //
        //			}
        //		}
        //		/// <summary>
        //		/// This Property gives access to the Link object which contains the link layer
        //		/// functionality. The link layer is responsible for the assembly and disassembly of packets,
        //		/// checksum calculations, DLE stuffing and ACK/NAK control for all GPS Devices connected
        //		/// through the serial port.
        //		/// The Link object can be accessed using this property.
        //		/// 
        //		/// The methods and properties shown are exposed to provide direct access to 
        //		/// GPS devices in order that additional protocols can be quickly added by client 
        //		/// software if required.
        //		/// 
        //		/// </summary>
        //		public Link Link
        //		{
        //			//-------------------------------------------------------------------------
        //			// ABSTRACT:
        //			//
        //			// PARAMETERS:
        //			//
        //			// RETURNS:
        //			//
        //			// DESCRIPTION:
        //			//
        //			//-------------------------------------------------------------------------
        //			
        //			get
        //			{
        //				
        //				try
        //				{
        //					//this gets created when the interface class is initialised
        //					return mLink;
        //				}
        //				catch(Exception e)
        //				{
        //					TraceLog.WriteError(e);
        //					throw new Exceptions.GeneralException(e.Message, e);
        //				}
        //			}
        //		}

        private static string Truncate(string sString, int iChars)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     Truncates a string to the number of characters passed
            //
            // PARAMETERS:   sString - String to truncate
            //               If the string is longer than iChars then it is truncated
            //               to iChars characters long.
            //
            // RETURNS:      String containing the padded result.
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            int lLength = 0;

            try
            {
                lLength = sString.Length;
                if (lLength > iChars)
                {
                    return sString.Substring(0, iChars);
                }
                else
                {
                    return sString;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }


        private byte[] PopulatePacketFromString(string Bytes)
        {
            //this function is typically used to populate a Pid from some logged data
            //and is only used during testing see CreateTrackpointObject (protocol 301 for example of use)
            byte[] bytPacket;
            try
            {
                string[] strBytes = Bytes.Split(" ".ToCharArray());

                //create a byte array as part of the passed in PidData
                int intUbound = strBytes.GetUpperBound(0);
                bytPacket = new byte[intUbound + 1];

                for (int f = 0; f <= intUbound; f++)
                {
                    //copy the string data and convert to byte values as we go
                    bytPacket[f] = Convert.ToByte(strBytes[f], 16);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return bytPacket;
        }

        private void SetTransportProtocol(LinkProtocol iProt)
        {

            if (mbUSBTransport)
                mUSB.Protocol = iProt;
            else
                mLink.Protocol = iProt;

        }
        private void SetDCValues(DCProtocol iProt)
        {
            //---------------------------------------------------------------------------
            //   DESCRIPTION:    Sets the Device Command ID valuse depending upon the
            //                   protocol passed. This procedure is called when this
            //                   class is initialised to set the default values.
            //
            //
            //   PARAMETERS:
            //                   iProt       Enumerated value indicating either A010 or
            //                               A011.
            //
            //   RETURNS:
            //                   <none>
            //
            //---------------------------------------------------------------------------

            try
            {
                //set all elements to -1 (not suported)
                for (int f = 0; f < miDCdata.Length; f++)
                {
                    miDCdata.SetValue(NOSUPPORT, f);
                }

                switch (iProt)
                {
                    case DCProtocol.A010:

                        miDCdata.SetValue(0, 0);             //miDCdata(0) = 0 'Abort Transfer
                        miDCdata.SetValue(1, 1);             //miDCdata(1) = 1 'Transfer Alm
                        miDCdata.SetValue(2, 2);             //miDCdata(2) = 2 'Transfer Pos
                        miDCdata.SetValue(3, 3);             //miDCdata(3) = 3 'Transfer Prx
                        miDCdata.SetValue(4, 4);             //miDCdata(4) = 4 'Transfer Rte
                        miDCdata.SetValue(5, 5);             //miDCdata(5) = 5 'Transfer Time
                        miDCdata.SetValue(6, 6);             //miDCdata(6) = 6 'Transfer Trk
                        miDCdata.SetValue(7, 7);             //miDCdata(7) = 7 'Transfer Wpt
                        miDCdata.SetValue(8, 8);             //miDCdata(8) = 8 'Turn Off Pwr
                        miDCdata.SetValue(49, 9);            //miDCdata(8) = 49 'Start Pvt
                        miDCdata.SetValue(50, 10);           //miDCdata(10) = 50 'Stop Pvt
                        miDCdata.SetValue(92, 11);           //miDCdata(11) = 92 'Flightbook
                        miDCdata.SetValue(117, 12);          //miDCdata(12) = 117 'Lap
                        miDCdata.SetValue(121, 13);          //miDCdata(13) = 121 'Waypoint Category
                        miDCdata.SetValue(110, 14);          //miDCdata(14) = 110 'Receiver Measurement On
                        miDCdata.SetValue(111, 15);          //miDCdata(15) = 111 'Receiver Measurement Off
                        miDCdata.SetValue(14, 16);           //miDCdata(16) = 14 'Unit Id
                        break;

                    case DCProtocol.A011:

                        miDCdata.SetValue(0, 0);             //miDCdata(0) = 0 'Abort Transfer
                        miDCdata.SetValue(4, 1);             //miDCdata(1) = 4 'Transfer Alm
                        miDCdata.SetValue(NOSUPPORT, 2);     //miDCdata(2) = NOSUPPORT 'Transfer Pos
                        miDCdata.SetValue(NOSUPPORT, 3);     //miDCdata(3) = NOSUPPORT 'Transfer Prx
                        miDCdata.SetValue(8, 4);             //miDCdata(4) = 8 'Transfer Rte
                        miDCdata.SetValue(20, 5);            //miDCdata(5) = 20 'Transfer Time
                        miDCdata.SetValue(NOSUPPORT, 6);     //miDCdata(6) = NOSUPPORT 'Transfer Trk
                        miDCdata.SetValue(21, 7);            //miDCdata(7) = 21 'Transfer Wpt
                        miDCdata.SetValue(26, 8);            //miDCdata(8) = 26 'Turn Off Pwr
                        miDCdata.SetValue(NOSUPPORT, 9);     //miDCdata(9) = NOSUPPORT
                        miDCdata.SetValue(NOSUPPORT, 10);    //miDCdata(10) = NOSUPPORT
                        miDCdata.SetValue(NOSUPPORT, 11);    //miDCdata(11) = NOSUPPORT
                        miDCdata.SetValue(NOSUPPORT, 12);    //miDCdata(12) = NOSUPPORT
                        miDCdata.SetValue(NOSUPPORT, 13);    //miDCdata(13) = NOSUPPORT
                        miDCdata.SetValue(NOSUPPORT, 14);    //miDCdata(14) = NOSUPPORT
                        miDCdata.SetValue(NOSUPPORT, 15);    //miDCdata(14) = NOSUPPORT
                        miDCdata.SetValue(14, 16);           //miDCdata(16) = 14 'Unit Id
                        break;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private TracklogCollection SplitTrackLog(Tracklog oTracklog)
        {

            TracklogCollection objTracklogs = null;
            Tracklog objTracklog = null;
            int intTrackCount = 0;

            try
            {
                //create a tracklogs collection
                objTracklogs = new TracklogCollection();

                //must check number of trackpoints > 0
                if (oTracklog.Trackpoints.Count > 0)
                {

                    //must ensure first trackpoint in tracklog has the new track property set to true
                    oTracklog.Trackpoints[0].NewTrack = true;

                    foreach (Trackpoint objTrackpoint in oTracklog.Trackpoints)
                    {

                        if (objTrackpoint.NewTrack)
                        {

                            //create a tracklog object
                            objTracklog = new Tracklog();

                            //used for the identifier
                            intTrackCount++;

                            //copy details from original
                            objTracklog.Colour = oTracklog.Colour;
                            objTracklog.Display = oTracklog.Display;

                            //TODO: ensure we dont exceed legth of identifier
                            //this is set at 50 for all protocols

                            objTracklog.Identifier = oTracklog.Identifier + " " + intTrackCount.ToString(CultureInfo.InvariantCulture);
                            objTracklog.Index = oTracklog.Index;

                            //add this to the collection
                            objTracklogs.Add(objTracklog);

                        }

                        objTracklog.Trackpoints.Add(objTrackpoint);

                    }
                }
                else
                {
                    //no trackpoints so return collection with single tracklog
                    objTracklogs.Add(oTracklog);

                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return objTracklogs;

        }

        private byte[] CreateWaypointArray(Waypoint oWaypoint)
        {
            return CreateWaypointArray(oWaypoint, WaypointType.Waypoint);
        }
        private byte[] CreateWaypointArray(Waypoint oWaypoint, WaypointType WaypointType)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:     This function creates a byte array from the object passed.
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------
            //
            byte[] Packet = null;
            //byte[] bytData = null; //this is used as temp storage to transfer the data
            //int iData = 0;
            //int lData = 0;
            //string sText = "";
            string sDataType = "";
            //int lByteCount = 0;

            try
            {
                //get SymbolId (if any) and update the symbol property
                //only do this if SymbolIdentifier has been explicitly set
                if (oWaypoint.SymbolIdentifier != SymbolId.Unknown)
                {
                    oWaypoint.Symbol = (short)GetSymbolValue(oWaypoint.SymbolIdentifier);
                }

                //determine data type to use
                switch (WaypointType)
                {
                    case WaypointType.Waypoint:
                        sDataType = mcProtocolTable["WaypointD0"].ToString();
                        break;
                    case WaypointType.Proximity:
                        sDataType = mcProtocolTable["ProximityD0"].ToString();
                        break;
                    case WaypointType.Route:
                        sDataType = mcProtocolTable["RouteD1"].ToString();
                        break;
                }

                //do the common properties, however these dont apply to D105, D106, D108 and D150
                if (sDataType != "D105" &&
                    sDataType != "D106" &&
                    sDataType != "D108" &&
                    sDataType != "D109" &&
                    sDataType != "D110" &&
                    sDataType != "D150")
                {
                    Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), 6)));
                    Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                    Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                    Packet = ByteConcat(Packet, IntToByte(oWaypoint.Unused1));
                    Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Comment, CharSet.WaypointComment) + "\0", 40)));
                }

                //the same process is used as that above so no comments added
                switch (sDataType)
                {
                    case "D100":
                    case "D400":
                        if (sDataType == "D400")
                            Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        break;
                    case "D101":
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol, true));
                        break;
                    case "D102":
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Symbol));
                        break;
                    case "D103":
                    case "D403":
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol, true));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Display, true));

                        if (sDataType == "D403")
                            Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        break;
                    case "D104":
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Display, true));
                        break;
                    case "D105":
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                        Packet = ByteConcat(Packet, StringToByte(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), true));
                        break;
                    case "D106":
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));
                        Packet = ByteConcat(Packet, PadByte(oWaypoint.Subclass.ToByteArray(), 13));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                        Packet = ByteConcat(Packet, StringToByte(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier) + "\0" + oWaypoint.Link + "\0"));
                        break;
                    case "D107":
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol, true));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Display, true));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, ShortToByte((short)oWaypoint.Colour, true));
                        break;
                    case "D108":
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));
                        Packet = ByteConcat(Packet, ShortToByte((short)oWaypoint.Colour, true));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Display, true));

                        if (oWaypoint.Attributes == 0)
                            Packet = ByteConcat(Packet, ShortToByte(96, true));
                        else
                            Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Attributes, true));
                        
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                        Packet = ByteConcat(Packet, PadByte(oWaypoint.Subclass.ToByteArray(), 18));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Altitude));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Depth));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.State, CharSet.State), 2)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Country, CharSet.CountryCode), 2)));
                        Packet = ByteConcat(Packet, StringToByte(Truncate(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), 50) + "\0" + Truncate(CharacterSetTrim(oWaypoint.Comment, CharSet.WaypointComment), 50) + "\0" + Truncate(CharacterSetTrim(oWaypoint.Facility, CharSet.FacilityName), 30) + "\0" + Truncate(CharacterSetTrim(oWaypoint.City, CharSet.City), 24) + "\0" + Truncate(oWaypoint.Address, 50) + "\0" + Truncate(oWaypoint.Crossroad, 50) + "\0"));
                        break;
                    case "D109":
                        Packet = ByteConcat(Packet, ShortToByte(1, true));
                        //Packet = ByteConcat(Packet, ShortToByte(oWaypoint.PacketType, true));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));
                        Packet = ByteConcat(Packet, ShortToByte((short)oWaypoint.Colour, true));

                        if (oWaypoint.Attributes == 0)
                            Packet = ByteConcat(Packet, ShortToByte(112, true));
                        else
                            Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Attributes, true));

                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                        Packet = ByteConcat(Packet, PadByte(oWaypoint.Subclass.ToByteArray(), 18));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Altitude));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Depth));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.State, CharSet.State), 2)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Country, CharSet.CountryCode), 2)));
                        Packet = ByteConcat(Packet, IntToByte(oWaypoint.TimeENRoute));

                        Packet = ByteConcat(Packet, StringToByte(Truncate(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), 50) + "\0" + Truncate(CharacterSetTrim(oWaypoint.Comment, CharSet.WaypointComment), 50) + "\0" + Truncate(CharacterSetTrim(oWaypoint.Facility, CharSet.FacilityName), 30) + "\0" + Truncate(CharacterSetTrim(oWaypoint.City, CharSet.City), 24) + "\0" + Truncate(oWaypoint.Address, 50) + "\0" + Truncate(oWaypoint.Crossroad, 50) + "\0"));
                        break;
                    case "D110":
                        Packet = ByteConcat(Packet, ShortToByte(1, true));
                        //Packet = ByteConcat(Packet, ShortToByte(oWaypoint.PacketType, true));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));
                        Packet = ByteConcat(Packet, ShortToByte((short)oWaypoint.Colour, true));

                        if (oWaypoint.Attributes == 0)
                            Packet = ByteConcat(Packet, ShortToByte(128, true));
                        else
                            Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Attributes, true));

                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                        Packet = ByteConcat(Packet, PadByte(oWaypoint.Subclass.ToByteArray(), 18));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Altitude));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Depth));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.State, CharSet.State), 2)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Country, CharSet.CountryCode), 2)));
                        Packet = ByteConcat(Packet, IntToByte(oWaypoint.TimeENRoute));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Temperature));
                        Packet = ByteConcat(Packet, IntToByte(oWaypoint.Timestamp));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Category));

                        Packet = ByteConcat(Packet, StringToByte(Truncate(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), 50) + "\0" + Truncate(CharacterSetTrim(oWaypoint.Comment, CharSet.WaypointComment), 50) + "\0" + Truncate(CharacterSetTrim(oWaypoint.Facility, CharSet.FacilityName), 30) + "\0" + Truncate(CharacterSetTrim(oWaypoint.City, CharSet.City), 24) + "\0" + Truncate(oWaypoint.Address, 50) + "\0" + Truncate(oWaypoint.Crossroad, 50) + "\0"));
                        break;
                    case "D150":

                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), 6)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Country, CharSet.CountryCode), 2)));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Altitude));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.City, CharSet.City), 24)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.State, CharSet.State), 2)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Facility, CharSet.FacilityName), 30)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Comment, CharSet.WaypointComment), 40)));
                        break;
                    case "D151":
                    case "D152":
                    case "D154":
                    case "D155":
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Facility, CharSet.FacilityName), 30)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.City, CharSet.City), 24)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.State, CharSet.State), 2)));
                        Packet = ByteConcat(Packet, ShortToByte((short)oWaypoint.Altitude));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Country, CharSet.CountryCode), 2)));
                        Packet = ByteConcat(Packet, ShortToByte((short)oWaypoint.Unused2, true));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));

                        if (sDataType == "D154")
                            Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));

                        if (sDataType == "D155")
                        {
                            Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Symbol));
                            Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Display, true));
                        }
                        break;
                    case "D450":

                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.ProximityIndex));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Identifier, CharSet.UserWaypointIdentifier), 6)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Country, CharSet.CountryCode), 2)));
                        Packet = ByteConcat(Packet, ShortToByte(oWaypoint.Class, true));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Latitude)));
                        Packet = ByteConcat(Packet, IntToByte(DegreesToSemicircles(oWaypoint.Longitude)));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.Altitude));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.City, CharSet.City), 24)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.State, CharSet.State), 2)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Facility, CharSet.FacilityName), 30)));
                        Packet = ByteConcat(Packet, StringToByte(Pad(CharacterSetTrim(oWaypoint.Comment, CharSet.WaypointComment), 40)));
                        Packet = ByteConcat(Packet, SingleToByte(oWaypoint.ProximityDistance));
                        break;
                    default:

                        throw new CreateWaypointException(ERR_MSG_3002);
                }
                return Packet;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private void DeviceCommand(CommandID CID)
        {
            //---------------------------------------------------------------------------
            //   DESCRIPTION:    Sends the Device Command to the GPS.
            //
            //
            //   PARAMETERS:
            //                   CommandID       Enumerated integer indicating the
            //                                   command to send. This enumerated value
            //                                   converted to the actual Command ID
            //                                   depending upon the protocol set in the
            //                                   DeviceCommandProtocol property.
            //
            //   RETURNS:
            //                   <none>
            //
            //---------------------------------------------------------------------------

            byte[] Packet = new byte[2]; //declare an empty 2 byte packet for the data
            GpsPidData PData; //create user variable to pass to the link layer
            //transmit function

            try
            {
                //populate the user defined type
                PData.ByteData = ShortToByte((short)(miDCdata[(int)CID]));
                PData.Pid = ProcessId.CommandData;

                //transmit the packet passing the user defined type
                TransmitData(PData, false);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        private string[] CreateProtocolTable(byte[] ProtocolArray)
        {
            //---------------------------------------------------------------------------
            //   DESCRIPTION:    This function takes a byte array containing the protocol
            //                   capabilities data and converts it into a string array
            //                   containing the string values in order.
            //
            //
            //   PARAMETERS:
            //                   ProtocolArray   Array of bytes that represent the data
            //                                   received from the GPS
            //
            //   RETURNS:
            //                   String()        Array of strings containing the
            //                                   supported protocols and their
            //                                   respective data types.
            //
            //---------------------------------------------------------------------------


            int iByte = 0;
            string sTag = "";
            string sData = "";
            int iData = 0;
            string[] sProtocol = null;

            try
            {
                //check length of array
                if (ProtocolArray.Length % 3 == 0) //array is a good size i.e. multiple of three bytes
                {
                    for (iByte = 0; iByte < ProtocolArray.Length; iByte = iByte + 3)
                    {
                        //copy the first char followed by the word in the next two bytes
                        sTag = ProtocolArray[iByte].ToString(CultureInfo.InvariantCulture);
                        iData = ByteToShort(ProtocolArray, iByte + 1);

                        //combine the lot into a string
                        sData = iData.ToString("000");

                        //add them to the string array
                        sProtocol = ReDimPreserveS(sProtocol, iByte / 3);
                        sProtocol[iByte / 3] = sTag + sData;
                    }
                }
                else
                {
                    //mabe raise an error here as the length supplied is incorrect
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            return sProtocol;
        }
        private System.Collections.Hashtable CreateProtocolCollectionFromProductInfo(int intProductID, int intSoftwareVersion)
        {
            //On Error GoTo Err_In_CreateProtocolCollectionFromProductInfo
            //-------------------------------------------------------------------------
            // ABSTRACT:     A simple routine to set protocol manually
            //
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------

            System.Collections.Hashtable cProtocol = new System.Collections.Hashtable();
            try
            {
                //add the common protocol
                cProtocol.Add("P000", "Pysical");

                //TODO: Make these constants

                switch (intProductID)
                {
                    case 7:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A500, D500");
                        break;
                    case 25:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 13:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 14:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A400, D400, A500, D500");
                        break;
                    case 15:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D151, A200, D200, D151, A400, D151, A500, D500");
                        break;
                    case 18:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 20:
                        cProtocol = CreateProtocolsFromString("L002, A011, A100, D150, A200, D201, D150, A400, D450, A500, D550");
                        break;
                    case 22:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D200, D152, A300, D300, A400, D152, A500, D500");
                        break;
                    case 23:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 24:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 29:
                        if (intSoftwareVersion < 400)
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D101, A200, D201, D101, A300, D300, A400, D101, A500, D500");
                        else
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D102, A200, D201, D102, A300, D300, A400, D102, A500, D500");
                        break;
                    case 31:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 33:
                        cProtocol = CreateProtocolsFromString("L002, A011, A100, D150, A200, D201, D150, A400, D450, A500, D550");
                        break;
                    case 34:
                        cProtocol = CreateProtocolsFromString("L002, A011, A100, D150, A200, D201, D150, A400, D450, A500, D550");
                        break;
                    case 35:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 36:
                        if (intSoftwareVersion < 300)
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D200, D152, A300, D300, A400, D152, A500, D500");
                        else
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D200, D152, A300, D300, A500, D500");
                        break;
                    case 39:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D151, A200, D201, D151, A300, D300, A500, D500");
                        break;
                    case 41:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 42:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D200, D100, A300, D300, A400, D400, A500, D500");
                        break;
                    case 44:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D101, A200, D201, D101, A300, D300, A400, D101, A500, D500");
                        break;
                    case 45:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D201, D152, A300, D300, A500, D500");
                        break;
                    case 47:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 48:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D154, A200, D201, D154, A300, D300, A500, D501");
                        break;
                    case 49:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D102, A200, D201, D102, A300, D300, A400, D102, A500, D501");
                        break;
                    case 50:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D201, D152, A300, D300, A500, D501");
                        break;
                    case 52:
                        cProtocol = CreateProtocolsFromString("L002, A011, A100, D150, A200, D201, D150, A400, D450, A500, D550");
                        break;
                    case 53:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D201, D152, A300, D300, A500, D501");
                        break;
                    case 55:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 56:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 59:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 61:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 62:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 64:
                        cProtocol = CreateProtocolsFromString("L002, A011, A100, D150, A200, D201, D150, A400, D450, A500, D551");
                        break;
                    case 71:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D155, A200, D201, D155, A300, D300, A500, D501");
                        break;
                    case 72:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D104, A200, D201, D104, A300, D300, A500, D501");
                        break;
                    case 73:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A500, D501");
                        break;
                    case 74:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A500, D500");
                        break;
                    case 76:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D102, A200, D201, D102, A300, D300, A400, D102, A500, D501");
                        break;
                    case 77:
                        if (intSoftwareVersion < 301)
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D100, A200, D201, D100, A300, D300, A400, D400, A500, D501");
                        else if (intSoftwareVersion >= 301 && intSoftwareVersion < 350)
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        else if (intSoftwareVersion >= 350 && intSoftwareVersion < 361)
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A500, D501");
                        else
                            cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 87:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 88:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D102, A200, D201, D102, A300, D300, A400, D102, A500, D501");
                        break;
                    case 95:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 96:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 97:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A500, D501");
                        break;
                    case 98:
                        cProtocol = CreateProtocolsFromString("L002, A011, A100, D150, A200, D201, D150, A400, D450, A500, D551");
                        break;
                    case 100:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 103:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 105:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 106:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501");
                        break;
                    case 112:
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D152, A200, D201, D152, A300, D300, A500, D501");
                        break;
                    default: //defaults to a GPS12
                        cProtocol = CreateProtocolsFromString("L001, A010, A100, D103, A200, D201, D103, A300, D300, A400, D403, A500, D501, A800, D800");
                        break;
                }

                //add more common protocols
                cProtocol.Add("A600", "Date");
                cProtocol.Add("D600", "DateD0");
                cProtocol.Add("A700", "Position");
                cProtocol.Add("D700", "PositionD0");
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return cProtocol;

        }

        private System.Collections.Hashtable CreateProtocolsFromString(string strProtocolString)
        {
            //-------------------------------------------------------------------------
            // ABSTRACT:   Takes comma delimited string of protocols and create a protocol
            //             table collection from them.
            // PARAMETERS:
            //
            // RETURNS:
            //
            // DESCRIPTION:
            //
            //-------------------------------------------------------------------------
            //

            string[] astrProtocols = null;
            string sDatatype = "";
            string sKey = "";
            int iDataType = 0;
            string sTag = "";
            string sData = "";
            string sTemp = "";

            System.Collections.Hashtable cProtocols = new System.Collections.Hashtable();

            try
            {
                //first separate the string into its components
                astrProtocols = strProtocolString.Split(",".ToCharArray());

                for (int Index = 0; Index < astrProtocols.Length; Index++)
                {
                    //reset the datatype variable (see case else)
                    sDatatype = "";
                    sTemp = astrProtocols[Index].Trim();
                    sTag = sTemp.Substring(0, 1);
                    sData = sTemp.Substring(1, sTemp.Length - 1);

                    //workout the key value
                    switch (string.Concat(sTag, sData))
                    {
                        case "P000":
                            sKey = "Physical";
                            iDataType = -1;
                            break;
                        case "L000":
                        case "L001":
                        case "L002":
                            sKey = "Link";
                            iDataType = -1;
                            break;
                        case "A010":
                            sKey = "Command";
                            iDataType = -1;
                            break;
                        case "A100":
                            sKey = "Waypoint";
                            iDataType = -1;
                            break;
                        case "A200":
                        case "A201":
                            sKey = "Route";
                            iDataType = -1;
                            break;
                        case "A300":
                        case "A301":
                        case "A302":
                            sKey = "Track";
                            iDataType = -1;
                            break;
                        case "A400":
                            sKey = "Proximity";
                            iDataType = -1;
                            break;
                        case "A500":
                            sKey = "Almanac";
                            iDataType = -1;
                            break;
                        case "A600":
                            sKey = "Date";
                            iDataType = -1;
                            break;
                        case "A650":
                            sKey = "FlightBook";
                            iDataType = -1;
                            break;
                        case "A700":
                            sKey = "Position";
                            iDataType = -1;
                            break;
                        case "A800":
                            sKey = "PVT";
                            iDataType = -1;
                            break;
                        default:

                            if (sTag == "D")
                            {
                                //must be a data type so add the data type key
                                //if the previous key was a datatype key then increment the number
                                iDataType = iDataType + 1;
                                sDatatype = "D" + iDataType.ToString(CultureInfo.InvariantCulture);
                            }
                            else
                            {
                                sKey = "Undocumented";
                                iDataType = -1;
                            }

                            break;

                    }

                    cProtocols.Add(string.Concat(sKey, sDatatype), string.Concat(sTag, sData));
                    mobjProdInfo.ProtocolTable = string.Concat(mobjProdInfo.ProtocolTable, sTag, sData, " ", Pad(sKey + " " + sDatatype, 16), "\r\n");


                }
                //remove last crlf
                mobjProdInfo.ProtocolTable = mobjProdInfo.ProtocolTable.Substring(0, mobjProdInfo.ProtocolTable.Length - 2);

                //log the protocol stuff
                TraceLog.WriteMessage("Protocol Table (Derived)", TraceLevel.Info, false, true);
                TraceLog.WriteMessage(mobjProdInfo.ProtocolTable, TraceLevel.Info, false, false, true);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return cProtocols;

        }

        private System.Collections.Hashtable CreateProtocolCollection(byte[] ProtocolArray)
        {
            //---------------------------------------------------------------------------
            //   DESCRIPTION:    This function takes a byte array containing the protocol
            //                   capabilities data and converts it into a collection
            //                   containing the string values in order. The key values
            //                   for the collection are in the format;
            //
            //                       "Waypoint"
            //                       "WaypointD0"
            //                       "Route"
            //                       "RouteD0"
            //                       "RouteD1"         etc.
            //
            //                   A tabulated string is also created for use during
            //                   debuging ant to return as a property.
            //
            //   PARAMETERS:
            //                   ProtocolArray   Array of bytes that represent the data
            //                                   received from the GPS
            //
            //   RETURNS:
            //                   Collection      Collection of strings containing the
            //                                   supported protocols and their
            //                                   respective data types.
            //
            //---------------------------------------------------------------------------


            int iByte = 0;
            string sTag = "";
            string sData = "";
            short iData = 0;
            System.Collections.Hashtable cProtocol = null;
            string sKey = "";
            //string sDataKey = "";
            string sDataType = "";
            int iDataType = 0;

            try
            {
                //check length of array
                if ((ProtocolArray.Length % 3) == 0)
                {
                    //array is a good size i.e. multiple of three bytes
                    //create an empty collection and empty string
                    cProtocol = new System.Collections.Hashtable();
                    mobjProdInfo.ProtocolTable = "";

                    for (iByte = 0; iByte < ProtocolArray.Length; iByte = iByte + 3)
                    {

                        //copy the first char followed by the word in the next two bytes
                        sTag = Microsoft.VisualBasic.Strings.Chr((int)ProtocolArray[iByte]).ToString(CultureInfo.InvariantCulture);
                        iData = ByteToShort(ProtocolArray, iByte + 1);

                        //combine the lot into a string
                        sData = iData.ToString("000");

                        //reset the datatype variable (see case else)
                        sDataType = "";

                        //workout the key value
                        switch (string.Concat(sTag, sData))
                        {
                            case "P000":
                                sKey = "Physical";
                                iDataType = -1;
                                break;
                            case "L000":
                            case "L001":
                            case "L002":
                                sKey = "Link";
                                iDataType = -1;
                                break;
                            case "A010":
                                sKey = "Command";
                                iDataType = -1;
                                break;
                            case "A100":
                                sKey = "Waypoint";
                                iDataType = -1;
                                break;
                            case "A200":
                            case "A201":
                                sKey = "Route";
                                iDataType = -1;
                                break;
                            case "A300":
                            case "A301":
                            case "A302":
                                sKey = "Track";
                                iDataType = -1;
                                break;
                            case "A400":
                                sKey = "Proximity";
                                iDataType = -1;
                                break;
                            case "A500":
                                sKey = "Almanac";
                                iDataType = -1;
                                break;
                            case "A600":
                                sKey = "Date";
                                iDataType = -1;
                                break;
                            case "A650":
                                sKey = "FlightBook";
                                iDataType = -1;
                                break;
                            case "A700":
                                sKey = "Position";
                                iDataType = -1;
                                break;
                            case "A800":
                                sKey = "PVT";
                                iDataType = -1;
                                break;
                            case "A906":
                                sKey = "Lap";
                                iDataType = -1;
                                break;
                            default:

                                if (sTag == "D")
                                {
                                    //must be a data type so add the data type key
                                    //if the previous key was a datatype key then increment the number
                                    iDataType = iDataType + 1;
                                    sDataType = "D" + iDataType.ToString(CultureInfo.InvariantCulture);
                                }
                                else
                                {
                                    sKey = "Unsupported";
                                    iDataType = -1;
                                }

                                break;

                        }

                        //add them to the collection and the module level string
                        string keyValue = string.Concat(sKey, sDataType);
                        if (!cProtocol.ContainsKey(keyValue))
                            cProtocol.Add(keyValue, string.Concat(sTag, sData));

                        mobjProdInfo.ProtocolTable = string.Concat(mobjProdInfo.ProtocolTable, sTag, sData, " ", Pad(sKey + " " + sDataType, 16), "\r\n");

                    }

                    //remove last crlf
                    mobjProdInfo.ProtocolTable = mobjProdInfo.ProtocolTable.Substring(0, mobjProdInfo.ProtocolTable.Length - 2);

                    //log the protocol stuff
                    TraceLog.WriteMessage("Protocol Table", TraceLevel.Info, false, true);
                    TraceLog.WriteMessage(mobjProdInfo.ProtocolTable, TraceLevel.Info, false, false, true);
                }
                else
                {
                    //maybe raise an error here as the length supplied is incorrect
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return cProtocol;
        }
        /// <summary>
        /// Returns a SymbolId for a given symbol value. This function takes account of the various
        /// symbol sets in use by devices.
        /// </summary>
        /// <param name="symbolValue"></param>
        /// <returns></returns>
        private SymbolId GetSymbolId(int symbolValue)
        {
            SymbolId result = SymbolId.Unknown;

            switch (mobjProdInfo.SymbolSet)
            {
                case 0:
                    //in this case some of the symbols map to a symbol in symbol set 1
                    //all are defined here for simplicity
                    switch (symbolValue)
                    {
                        case 0:
                            result = SymbolId.Dot;
                            break;
                        case 1:
                            result = SymbolId.House;
                            break;
                        case 2:
                            result=SymbolId.Gas;
                            break;
                        case 3:
                            result=SymbolId.Car;
                            break;
                        case 4:
                            result=SymbolId.Fish;
                            break;
                        case 5:
                            result=SymbolId.Boat;
                            break;
                        case 6:
                            result=SymbolId.Anchor;
                            break;
                        case 7:
                            result=SymbolId.Wreck;
                            break;
                        case 8:
                            result=SymbolId.Exit;
                            break;
                        case 9:
                            result=SymbolId.Skull;
                            break;
                        case 10:
                            result=SymbolId.Flag;
                            break;
                        case 11:
                            result=SymbolId.Camp;
                            break;
                        case 12:
                            result=SymbolId.CircleX;
                            break;
                        case 13:
                            result=SymbolId.Deer;
                            break;
                        case 14:
                            result=SymbolId.FirstAid;
                            break;
                        case 15:
                            result = SymbolId.BackTrack;
                            break;
                    }
                    break;

                case 1:
                    //simply cast the value to the associated id
                    //must be within the limits of this symbol set
                    try
                    {
                        if (symbolValue < 256)
                            result = (SymbolId)symbolValue;
                        else
                            result = SymbolId.Unknown;
                    }
                    catch
                    {
                        result = SymbolId.Unknown;
                    }
                    break;
                case 2:
                    //simply cast the value to the associated id
                    //must be within the limits of this symbol set
                    try
                    {
                        if (symbolValue < 65536)
                            result = (SymbolId)symbolValue;
                        else
                            result = SymbolId.Unknown;
                    }
                    catch
                    {
                        result = SymbolId.Unknown;
                    }
                    break;
            }
            return result;
        }
        /// <summary>
        /// Returns a given symbol value for a given SymbolId this function takes account of the 
        /// various symbol sets etc in use.
        /// </summary>
        /// <param name="symbolId"></param>
        /// <returns></returns>
        private int GetSymbolValue(SymbolId symbolId)
        {
            int result = 0;

            switch (mobjProdInfo.SymbolSet)
            {
                case 0:
                    //in this case some of the symbols map to a symbol in symbol set 1
                    //all are defined here for simplicity
                    switch (symbolId)
                    {
                        case SymbolId.Dot:
                            result = 0;
                            break;
                        case SymbolId.House:
                            result = 1;
                            break;
                        case SymbolId.Gas:
                            result = 2;
                            break;
                        case SymbolId.Car:
                            result = 3;
                            break;
                        case SymbolId.Fish:
                            result = 4;
                            break;
                        case SymbolId.Boat:
                            result = 5;
                            break;
                        case SymbolId.Anchor:
                            result = 6;
                            break;
                        case SymbolId.Wreck:
                            result = 7;
                            break;
                        case SymbolId.Exit:
                            result = 8;
                            break;
                        case SymbolId.Skull:
                            result = 9;
                            break;
                        case SymbolId.Flag:
                            result = 10;
                            break;
                        case SymbolId.Camp:
                            result = 11;
                            break;
                        case SymbolId.CircleX:
                            result = 12;
                            break;
                        case SymbolId.Deer:
                            result = 13;
                            break;
                        case SymbolId.FirstAid:
                            result = 14;
                            break;
                        case SymbolId.BackTrack:
                            result = 15;
                            break;
                    }
                    break;
                case 1:
                    //simply cast the value to the associated id 
                    //but this symbol set cannot handle vales greater than 255
                    result = (int)symbolId;
                    if (result > 255)
                        result = 18; //default
                    break;
                case 2:
                    //simply cast the value to the associated id
                    //but this symbol set cannot handle vales greater than 65535
                    result = (int)symbolId;
                    if (result > 65535)
                        result = 18; //default
                    break;
            }
            return result;
        }

#endregion

        //[jn] Removed as it causes problems with .Net 2 on XP

//#region Timer

//        /// <summary>
//        /// This class is the event arguments class used to pass argument values in the Tick event.
//        /// </summary>
//        public class DataEventArgs : EventArgs
//        {
//            /// <summary>
//            /// Constructor for the class.
//            /// </summary>
//            public DataEventArgs(Position position)
//            {
//                this.GpsData = position;
//            }
//            /// <summary>
//            /// Returns the gps data as a typed object.
//            /// </summary>
//            public readonly Position GpsData;
//        }
//        private void tickTimer_TimerCallback(object state)
//        {
//            //actually the data we get as a parameter is a reference to the 
//            //m_gpsData and m_nmeaError objects so just use that 
//            //instead of casting 'state' etc.
//            if (DataEvent != null)
//            {
//                Position currentPosition = GetPosition();
//                DataEvent(this, new DataEventArgs(currentPosition));
//            }
//        }
//        /// <summary>
//        /// Enables/disables the tick event.
//        /// </summary>
//        public bool DataEventEnabled
//        {
//            get
//            {
//                return m_enabled;
//            }
//            set
//            {
//                m_enabled = value;

//                if (value == true)
//                {
//                    //ComPort.Open();

//                    //get the gps data for the next tick, this needs to be more current
//                    m_tickTimer.Change(0, m_interval * 1000);
//                    //m_portTimer.Change(m_interval / 2 * 1000, m_interval * 1000);

//                }
//                else
//                {
//                    m_tickTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);
//                    //m_portTimer.Change(System.Threading.Timeout.Infinite, System.Threading.Timeout.Infinite);

//                    //ComPort.Close();
//                }
//            }
//        }
//        /// <summary>
//        /// Interval in seconds. Minimum value 5, maximum value 6000.
//        /// </summary>
//        public int DataEventInterval
//        {
//            set
//            {
//                if (value < 5)
//                    throw new ArgumentException(ERR_MSG_3018);
//                if(value > 6000)
//                    throw new ArgumentException(ERR_MSG_3019);
                
//                m_interval = value;

//            }
//            get
//            {
//                return m_interval;
//            }
//        }
//#endregion
    }

}
